home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Mac Mania 5
/
MacMania 5.toast
/
/
Internet software
/
NewsWatcher
/
NW Source
/
Source
/
article.c
< prev
next >
Wrap
Text File
|
1997-01-09
|
112KB
|
4,175 lines
/*----------------------------------------------------------------------------
article.c
This module handles article windows.
Copyright © 1994-1997, Northwestern University.
----------------------------------------------------------------------------*/
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <Icons.h>
#include "glob.h"
#include "article.h"
#include "child.h"
#include "newswatcher.h"
#include "dialog.h"
#include "header.h"
#include "mark.h"
#include "news.h"
#include "menus.h"
#include "datetime.h"
#include "print.h"
#include "next.h"
#include "message.h"
#include "cancel.h"
#include "status.h"
#include "strutil.h"
#include "tescroll.h"
#include "url.h"
#include "drawutil.h"
#include "memutil.h"
#include "windutil.h"
#include "teutil.h"
#include "wind.h"
#include "fileutil.h"
#include "sfutil.h"
#include "iconutil.h"
#include "apputil.h"
#include "dragutil.h"
#include "subject.h"
#include "group.h"
#include "key.h"
#include "search.h"
#include "ic.h"
#include "help.h"
#include "arrowpair.h"
#include "biglist.h"
#define kMinWindowWidth 400 /* minimum window width */
#define kSectionMargin 165 /* right coord of "Section x of y" rect */
#define kArticleFileExistsAlert 144
#define kTooStupidAlert 152
#define kFileIconID 203
#define kMaxCachedArticles 50 /* max number of cached articles */
#define kArticleCacheMemoryToKeepFree 150000L /* amount of memory to keep free when
caching articles */
typedef struct TCachedArticleInfo {
unsigned long timeCached; /* tickcount when entry added to cache */
Handle groupName; /* handle to group name, or nil if by id */
long number; /* article number in group if not by id */
Handle msgId; /* handle to message id if by id */
Handle txt; /* handle to full article text (purgable!) */
Boolean inUse; /* true if this cache entry is being used */
Boolean flagReqd; /* true if when fetched from server BinHex or uuencode
text was required to have a special "begin" flag line */
Boolean attachedFile; /* true if article contains an attached file */
} TCachedArticleInfo;
static TCachedArticleInfo **gArticleCache = nil; /* handle to article cache array */
static TEClickLoopUPP gAutoScrollUPP;
static DragSendDataUPP gDragAttachedFileIconSendProcUPP;
static ControlActionUPP gScrollActionUPP;
static ControlActionUPP gScrollActionSectionUPP;
/*----------------------------------------------------------------------------
SaveArticleInCache
Save an article in the article cache.
Entry: wind = pointer to article window.
When an article window is closed or reused, instead of disposing the article
text, we cache it in purgable memory in case the user reopens the article
in the near future (e.g., via the up arrow control in an article window).
This function transfers ownership of the full article text relocatable block
from the article window to the cache, and the block is made purgable.
Similarly, ownership of the two relocatable blocks containing the article
message id and/or group name are transfered from the window to the cache,
but these blocks are not made purgable.
----------------------------------------------------------------------------*/
static void SaveArticleInCache (WindowPtr wind)
{
TWindow **info;
OSErr err = noErr;
short i, oldestTimeCachedIndex;
Handle groupName, msgId, txt;
unsigned long oldestTimeCached;
TCachedArticleInfo *p;
info = (TWindow**)GetWRefCon(wind);
/* Do some sanity checks. */
if ((**info).fullText == nil) goto exit;
if ((**info).msgId == nil && (**info).groupName == nil) goto exit;
/* Allocate the cache info array on the first call. */
if (gArticleCache == nil) {
err = MyNewHandle(kMaxCachedArticles * sizeof(TCachedArticleInfo), &gArticleCache);
if (err != noErr) goto exit;
}
/* Search the cache to find either an available unused slot or the oldest used slot.
Treat any used slots with purged text as unused. */
oldestTimeCached = 0xffffffff;
oldestTimeCachedIndex = -1;
for (i = 0, p = *gArticleCache; i < kMaxCachedArticles; i++, p++) {
if (p->inUse && *p->txt != nil) {
if (p->timeCached < oldestTimeCached) {
oldestTimeCached = p->timeCached;
oldestTimeCachedIndex = i;
}
} else {
break;
}
}
if (i >= kMaxCachedArticles) i = oldestTimeCachedIndex;
/* Dispose any old entry which occupies the slot. */
p = *gArticleCache + i;
if (p->inUse) {
groupName = p->groupName;
msgId = p->msgId;
txt = p->txt;
MyDisposeHandle(groupName);
MyDisposeHandle(msgId);
MyDisposeHandle(txt);
}
/* Cache the article at slot "i" in the cache info array. Make the
article text purgable. */
p = *gArticleCache + i;
p->timeCached = TickCount();
p->groupName = (**info).groupName;
p->number = (**info).number;
p->msgId = (**info).msgId;
p->txt = (**info).fullText;
p->inUse = true;
p->flagReqd = (**info).flagReqd;
p->attachedFile = (**info).attachedFile;
(**info).groupName = nil;
(**info).msgId = nil;
(**info).fullText = nil;
HPurge(p->txt);
/* If we're getting low on free memory, purge the oldest cache entries.
This helps prevent filling up all the free memory with cached articles,
which would make the Memory Manager thrash around purging blocks all
the time (and most likely purging the wrong ones at that. */
while (FreeMem() < kArticleCacheMemoryToKeepFree) {
oldestTimeCached = 0xffffffff;
oldestTimeCachedIndex = -1;
for (i = 0, p = *gArticleCache; i < kMaxCachedArticles; i++, p++) {
if (p->inUse && p->timeCached < oldestTimeCached) {
oldestTimeCached = p->timeCached;
oldestTimeCachedIndex = i;
}
}
if (oldestTimeCachedIndex < 0) break;
p = *gArticleCache + oldestTimeCachedIndex;
groupName = p->groupName;
msgId = p->msgId;
txt = p->txt;
p->inUse = false;
MyDisposeHandle(groupName);
MyDisposeHandle(msgId);
MyDisposeHandle(txt);
}
return;
exit:
MyDisposeHandle((**info).fullText);
(**info).fullText = nil;
MyDisposeHandle((**info).msgId);
(**info).msgId = nil;
MyDisposeHandle((**info).groupName);
(**info).groupName = nil;
}
/*----------------------------------------------------------------------------
GetArticleFromCache
Get an article from the article cache.
Entry: groupName = name of group, or nil if fetching by message id.
number = article number. Ignored if fetching by message id.
id = message id string, including < and > delimiters. Ignored
if fetching by article number.
flagReqd = true if BinHex or uuencode text
must include special "begin" flag line.
Exit: function result = true if article found in cache.
*txt = handle to article text.
*length = length of article text.
*attachedFile = true if article contains an attached file.
If the article is found in the cache, ownership of the relocatable block
containing the full article text is transferred from the cache to the
caller, and the block is made non-purgable.
----------------------------------------------------------------------------*/
static Boolean GetArticleFromCache (char *groupName, long number, char *id,
Boolean flagReqd, Handle *txt, long *length, Boolean *attachedFile)
{
short i;
TCachedArticleInfo *p;
Boolean match;
Handle h1, h2;
if (gArticleCache == nil) return false;
/* Search the cache for the article. */
for (i = 0, p = *gArticleCache; i < kMaxCachedArticles; i++, p++) {
if (p->inUse && *p->txt != nil) {
if (groupName != nil && p->groupName != nil) {
match = MyStrEqual(groupName, *p->groupName) && number == p->number;
} else if (id != nil && p->msgId != nil) {
match = MyStrEqual(id, *p->msgId);
} else {
match = false;
}
match = match && flagReqd == p->flagReqd;
if (match) {
/* We found the article in the cache. Transfer ownership of the
text to our caller, make the text nonpurgable, and free the cache slot. */
*txt = p->txt;
HNoPurge(*txt);
*attachedFile = p->attachedFile;
p->inUse = false;
h1 = p->groupName;
h2 = p->msgId;
MyDisposeHandle(h1);
MyDisposeHandle(h2);
*length = MyGetHandleSize(*txt);
return true;
}
}
}
return false;
}
/*----------------------------------------------------------------------------
Rot13Text
Rot13 text.
Entry: text = handle to text.
start = offset in text to start.
end = offset in text to end.
----------------------------------------------------------------------------*/
void Rot13Text (Handle text, long start, long end)
{
char *p, *pEnd;
for (p = *text + start, pEnd = *text + end; p < pEnd; p++)
if (isalpha(*p)) *p += (toupper(*p) > 'M') ? -13 : 13;
}
/*----------------------------------------------------------------------------
GetTextRect
Compute the "text" rectangle of an article window
Entry: wind = pointer to article window.
Exit: *textRect = text rectangle.
The "text" rectangle is the area of the window where the text is
displayed: the window portrect, minus the panel area, minus the scroll
bars, minus the text margin.
----------------------------------------------------------------------------*/
static void GetTextRect (WindowPtr wind, Rect *textRect)
{
TWindow **info;
info = (TWindow**)GetWRefCon(wind);
*textRect = wind->portRect;
textRect->top += (**info).panelHeight;
textRect->bottom -= 15;
textRect->right -= 15;
InsetRect(textRect, kTextMargin, kTextMargin);
}
/*----------------------------------------------------------------------------
FixHeight
Round down window height to an exact multiple of lines.
Entry: wind = pointer to article window.
*height = window height.
Exit: *height = adjusted window height
----------------------------------------------------------------------------*/
static void FixHeight (WindowPtr wind, short *height)
{
TWindow **info;
short panelHeight, lineHeight, adjust;
info = (TWindow**)GetWRefCon(wind);
panelHeight = (**info).panelHeight;
lineHeight = (**info).lineHeight;
adjust = panelHeight + 15 + 2*kTextMargin;
*height = (*height - adjust) / lineHeight * lineHeight + adjust;
}
/*----------------------------------------------------------------------------
MinHeight
Compute the minimum height of an article window.
Entry: wind = pointer to article window.
----------------------------------------------------------------------------*/
static short MinHeight (WindowPtr wind)
{
TWindow **info;
short lineHeight, height, extra;
info = (TWindow**)GetWRefCon(wind);
lineHeight = (**info).lineHeight;
extra = lineHeight + 15 + 2*kTextMargin;
if (extra < 65) extra = 65 + lineHeight;
height = (**info).panelHeight + extra;
FixHeight(wind, &height);
return height;
}
/*----------------------------------------------------------------------------
DrawSectionMessage
Draw the "Section x of y" message in an article window.
Entry: wind = pointer to article window.
----------------------------------------------------------------------------*/
static void DrawSectionMessage (WindowPtr wind)
{
TWindow **info;
short fontNum;
CStr255 msg;
CStr255 sectionMessageFormat;
TextStyle savedStyle;
short width, left;
GetPortTextStyle(&savedStyle);
info = (TWindow**)GetWRefCon(wind);
MoveTo(16, wind->portRect.bottom - 15);
LineTo(16, wind->portRect.bottom);
GetCString(kStrSectionMessageFormat, sectionMessageFormat);
GetFontNumber("\pMonaco", &fontNum);
TextFont(fontNum);
TextSize(9);
sprintf(msg, sectionMessageFormat, (**info).curSection+1, (**info).numSections);
c2pstr(msg);
width = StringWidth((StringPtr)msg);
left = 16 + ((kSectionMargin - 46 - width) >> 1);
MoveTo(left, wind->portRect.bottom-4);
DrawString((StringPtr)msg);
DrawArrowPair((**info).sectionArrowPair);
MoveTo(kSectionMargin - 30, wind->portRect.bottom - 15);
LineTo(kSectionMargin - 30, wind->portRect.bottom);
MoveTo(kSectionMargin - 15, wind->portRect.bottom - 15);
LineTo(kSectionMargin - 15, wind->portRect.bottom);
MoveTo(kSectionMargin, wind->portRect.bottom - 15);
LineTo(kSectionMargin, wind->portRect.bottom);
SetPortTextStyle(&savedStyle);
}
/*----------------------------------------------------------------------------
DrawPanelString
Draw a string in the panel area of an article window.
Entry: wind = pointer to article window.
str = string to draw.
v = vertical coordinate of string.
width = max width of string.
----------------------------------------------------------------------------*/
static void DrawPanelString (char *str, short v, short width)
{
if (width < 20) return;
c2pstr(str);
TruncString(width, (StringPtr)str, smTruncEnd);
MoveTo(10, v);
DrawString((StringPtr)str);
p2cstr((StringPtr)str);
}
/*----------------------------------------------------------------------------
ScrollSection
Scroll an article window horizontally (by sections).
Entry: wind = pointer to article window.
dh = number of sections to scroll.
----------------------------------------------------------------------------*/
static void ScrollSection (WindowPtr wind, short dh)
{
TWindow **info;
TEHandle theTE;
short curSection, newSection;
Rect r, textRect;
Handle text;
long **breaks;
long offset, length;
char state;
info = (TWindow**)GetWRefCon(wind);
theTE = (**info).theTE;
GetTextRect(wind, &textRect);
curSection = (**info).curSection;
(**info).curSection = newSection = curSection - dh;
r = wind->portRect;
r.top = r.bottom - 13;
r.left += kTextMargin + 15;
r.bottom -= 2;
r.right = kSectionMargin - 32;
EraseRect(&r);
DrawSectionMessage(wind);
EnableOrDisableArrowPair((**info).sectionArrowPair, -1,
newSection > 0);
EnableOrDisableArrowPair((**info).sectionArrowPair, +1,
newSection < (**info).numSections - 1);
text = (**info).fullText;
breaks = (**info).sectionBreaks;
offset = (*breaks)[newSection];
length = (*breaks)[newSection+1] - offset;
if (newSection == 0 && !(**info).showDetails) {
offset = FindBody(text);
if (offset > length) offset = length;
length -= offset;
}
state = MyHGetState(text);
MyHLock(text);
TESetText(*text+offset, length, theTE);
MyHSetState(text, state);
TESetSelect(0, 0, theTE);
SetControlValue((**info).vScroll,0);
(**theTE).destRect = textRect;
TEScrollAdjustScrollMax(theTE, (**info).vScroll);
InvalRect(&textRect);
HandleUpdate(wind);
KillBalloon();
}
/*----------------------------------------------------------------------------
ScrollActionSection
Horizontal scroll bar action procedure (sections).
Entry: hScroll = handle to horizontal scroll bar control
part = part code.
----------------------------------------------------------------------------*/
static pascal void ScrollActionSection (ControlHandle hScroll, short part)
{
WindowPtr wind;
TWindow **info;
short val, max, dh;
wind = (**hScroll).contrlOwner;
info = (TWindow**)GetWRefCon(wind);
val = (**hScroll).contrlValue;
max = (**hScroll).contrlMax;
dh = 0;
switch (part) {
case inUpButton:
case inPageUp:
dh = val > 0 ? 1 : 0;
break;
case inDownButton:
case inPageDown:
dh = val < max ? -1 : 0;
break;
case kScrollToHome:
dh = val;
break;
case kScrollToEnd:
dh = val - max;
break;
}
if (dh != 0) {
SetControlValue(hScroll, val - dh);
ScrollSection(wind, dh);
}
}
/*----------------------------------------------------------------------------
ScrollAction
Vertical scroll bar action proc.
Entry: vScroll = handle to vertical scroll bar control.
part = part code.
----------------------------------------------------------------------------*/
static pascal void ScrollAction (ControlHandle vScroll, short part)
{
WindowPtr wind;
TWindow **info;
TEHandle theTE;
wind = (**vScroll).contrlOwner;
info = (TWindow**)GetWRefCon(wind);
theTE = (**info).theTE;
TEScrollScrollByPartCode(theTE, vScroll, part);
}
/*----------------------------------------------------------------------------
AutoScroll
Handle article window autoscrolling.
Exit: function result = true
----------------------------------------------------------------------------*/
static pascal Boolean AutoScroll (void)
{
WindowPtr wind;
TWindow **info;
ControlHandle vScroll;
TEHandle theTE;
wind = MyFrontWindow();
if (wind == nil) return true;
info = (TWindow**)GetWRefCon(wind);
vScroll = (**info).vScroll;
theTE = (**info).theTE;
TEScrollAutoScroll(theTE, vScroll);
return true;
}
/*----------------------------------------------------------------------------
ResizeContents
Adjust an article window's contents after a window size change (grow
or zoom).
Entry: wind = pointer to article window.
----------------------------------------------------------------------------*/
static void ResizeContents (WindowPtr wind)
{
TWindow **info;
short width, height, panelHeight;
ControlHandle hScroll, vScroll;
TEHandle theTE;
Rect r;
Point sectionArrowPairBotLeft, nextPrevArrowPairBotLeft;
info = (TWindow**)GetWRefCon(wind);
panelHeight = (**info).panelHeight;
vScroll = (**info).vScroll;
hScroll = (**info).hScroll;
theTE = (**info).theTE;
width = wind->portRect.right;
height = wind->portRect.bottom;
SetRect(&r, width-15, panelHeight-1, width+1, height-14);
(**vScroll).contrlRect = r;
if (hScroll != nil) {
SetRect(&r, kSectionMargin, height-15, width-14, height+1);
(**hScroll).contrlRect = r;
}
GetTextRect(wind, &r);
(**theTE).viewRect = (**theTE).destRect = r;
TECalText(theTE);
SetControlValue(vScroll, 0);
TEScrollAdjustScrollMax(theTE, vScroll);
TEScrollScrollSelectionIntoView(theTE, vScroll);
if ((**info).sectionArrowPair != nil) {
SetPt(§ionArrowPairBotLeft, kSectionMargin - 26, height - 3);
MoveArrowPair((**info).sectionArrowPair, sectionArrowPairBotLeft);
}
if ((**info).nextPrevArrowPair != nil) {
SetPt(&nextPrevArrowPairBotLeft, wind->portRect.right - 17, 30);
MoveArrowPair((**info).nextPrevArrowPair, nextPrevArrowPairBotLeft);
}
InvalRect(&wind->portRect);
RecordNewLockedWindowPosition(wind, &gPrefs.articleWindLocn);
}
/*----------------------------------------------------------------------------
FindNextSectionBreak
Find the next section break in an article
Entry: fullText = handle to full article text.
len = length of full article text.
offset = offset in full article text of previous break.
Exit: function result = offset in full article text of next break
----------------------------------------------------------------------------*/
static long FindNextSectionBreak (Handle fullText, long len, long offset)
{
char *p, *pEnd, *qStart, *qEnd, *q, *s, *qCR, *qCRCR;
p = *fullText + offset;
pEnd = *fullText + len;
qEnd = p + 31999;
if (qEnd > pEnd) qEnd = pEnd;
for (q = p; q < qEnd; q++) {
if (*q == FF) {
return q - *fullText + 1;
}
}
if (qEnd >= pEnd) return len;
qStart = p + 31999;
qCR = qCRCR = qEnd = p + 25010;
for (q = qStart; q > qEnd; q--) {
if (*q == CR) {
qCR = q;
if (*(q-1) == CR) {
qCRCR = q;
for (s = q-2; *s == '-' && s > qEnd; s--) /* do nothing */;
if (*s == CR) break;
}
}
}
if (q <= qEnd) q = qCRCR;
if (q <= qEnd) q = qCR;
if (q <= qEnd) q = qStart;
return q - *fullText;
}
/*----------------------------------------------------------------------------
MakeSections
Split a large (>32K) block of text into sections.
Entry: text = handle to large block of text.
Exit: function result = error code.
*sectionBreaks = section breaks.
*numSections = number of sections.
----------------------------------------------------------------------------*/
OSErr MakeSections (Handle text, long ***sectionBreaks, short *numSections)
{
long **breaks = nil;
long len, offset, breaksAllocated;
short n;
OSErr err = noErr;
len = MyGetHandleSize(text);
err = MyNewHandle(10*sizeof(long), &breaks);
if (err != noErr) goto exit;
breaksAllocated = 10;
offset = 0;
n = 0;
while (offset < len) {
if (n+1 >= breaksAllocated) {
breaksAllocated += 10;
err = MySetHandleSize(breaks, breaksAllocated*sizeof(long));
if (err != noErr) goto exit;
}
(*breaks)[n++] = offset;
offset = FindNextSectionBreak(text, len, offset);
}
(*breaks)[n] = len;
MySetHandleSize(breaks, (n+1)*sizeof(long));
*numSections = n;
*sectionBreaks = breaks;
return noErr;
exit:
MyDisposeHandle(breaks);
return err;
}
/*----------------------------------------------------------------------------
CalcSectionBreaks
Calculate section breaks for an article larger than 32K and create the
horizontal scroll bar.
Entry: wind = pointer to article window.
Exit: function result = error code.
----------------------------------------------------------------------------*/
static OSErr CalcSectionBreaks (WindowPtr wind)
{
TWindow **info;
Handle fullText;
long **breaks = nil;
short numSections;
Rect r;
OSErr err = noErr;
info = (TWindow**)GetWRefCon(wind);
fullText = (**info).fullText;
err = MakeSections(fullText, &breaks, &numSections);
if (err != noErr) goto exit;
(**info).numSections = numSections;
(**info).curSection = 0;
(**info).sectionBreaks = breaks;
if (numSections > 1) {
SetRect(&r, kSectionMargin, wind->portRect.bottom - 15,
wind->portRect.right - 14, wind->portRect.bottom + 1);
(**info).hScroll = NewControl(wind, &r, "\p", true, 0, 0,
numSections-1, scrollBarProc, 0);
}
return noErr;
exit:
MyDisposeHandle(breaks);
return err;
}
/*----------------------------------------------------------------------------
ComputePanelInfo
Compute information about the panel area of a new article window.
Entry: wind = pointer to article window.
txt = handle to full article text.
Exit: function result = error code.
----------------------------------------------------------------------------*/
static OSErr ComputePanelInfo (WindowPtr wind, Handle txt)
{
TWindow **info;
short lineHeight;
short panelHeight;
short numPanelLines;
Handle from = nil;
Handle newsgroups = nil;
Handle followupto = nil;
Handle replyto = nil;
long fromLen, newsgroupsLen, followuptoLen, replytoLen;
OSErr err = noErr;
info = (TWindow**)GetWRefCon(wind);
lineHeight = (**info).lineHeight;
if (lineHeight < 11) lineHeight = 11;
err = FindHeaderHandle(txt, "From", &from);
if (err != noErr) goto exit;
err = FindHeaderHandle(txt, "Newsgroups", &newsgroups);
if (err != noErr) goto exit;
err = FindHeaderHandle(txt, "Followup-To", &followupto);
if (err != noErr) goto exit;
err = FindHeaderHandle(txt, "Reply-To", &replyto);
if (err != noErr) goto exit;
(**info).showReplyTo = (**info).showFollowupTo = false;
if (from != nil && replyto != nil) {
fromLen = MyGetHandleSize(from);
replytoLen = MyGetHandleSize(replyto);
(**info).showReplyTo = fromLen != replytoLen ||
!MyStrNEqual(*from, *replyto, fromLen);
}
if (newsgroups != nil && followupto != nil) {
newsgroupsLen = MyGetHandleSize(newsgroups);
followuptoLen = MyGetHandleSize(followupto);
(**info).showFollowupTo = newsgroupsLen != followuptoLen ||
!MyStrNEqual(*newsgroups, *followupto, newsgroupsLen);
}
numPanelLines = 4;
if ((**info).showReplyTo) numPanelLines++;
if ((**info).showFollowupTo) numPanelLines++;
panelHeight = numPanelLines*lineHeight + 9;
(**info).panelHeight = panelHeight;
exit:
MyDisposeHandle(from);
MyDisposeHandle(newsgroups);
MyDisposeHandle(followupto);
MyDisposeHandle(replyto);
return err;
}
/*----------------------------------------------------------------------------
CheckFirstLastArticle
Check to see if an article is the first or last one in its parent subject
window.
Entry: wind = pointer to article window.
Exit: *first = true if article is first one.
*last = true if article is last one.
----------------------------------------------------------------------------*/
static void CheckFirstLastArticle (WindowPtr wind, Boolean *first, Boolean *last)
{
TWindow **info, **parentInfo;
WindowPtr parentWindow;
TSubject **subjectArray;
long item, index, lastItem;
TSubject theSubject;
BigListRef subjectList;
info = (TWindow**)GetWRefCon(wind);
parentWindow = (**info).parentWindow;
if (parentWindow == nil) return;
index = (**info).parentSubject;
parentInfo = (TWindow**)GetWRefCon(parentWindow);
subjectArray = (**parentInfo).subjectArray;
theSubject = (*subjectArray)[index];
subjectList = (**parentInfo).subjectList;
lastItem = BigLGetNumItems(subjectList) - 1;
if (theSubject.collapsed) {
item = FindParentItem(parentWindow, theSubject.threadHeadIndex);
*first = item == 0 && theSubject.threadOrdinal == 1;
*last = item == lastItem && theSubject.threadOrdinal == theSubject.threadLength;
} else {
item = FindParentItem(parentWindow, index);
*first = item == 0;
*last = item == lastItem;
}
}
/*----------------------------------------------------------------------------
EnableOrDisableNextPrevArrows
Enable/disable the next/prev article arrow controls in an article window.
Entry: wind = pointer to article window.
The next article (down) arrow is enabled iff this article is not the
last article in the parent subject window.
The prev article (up) arrow is enabled iff this article is not the
first article in the parent subject window.
----------------------------------------------------------------------------*/
static void EnableOrDisableNextPrevArrows (WindowPtr wind)
{
TWindow **info;
Boolean first, last;
ArrowPairRef nextPrevArrowPair;
info = (TWindow**)GetWRefCon(wind);
nextPrevArrowPair = (**info).nextPrevArrowPair;
CheckFirstLastArticle(wind, &first, &last);
EnableOrDisableArrowPair(nextPrevArrowPair, -1, !first);
EnableOrDisableArrowPair(nextPrevArrowPair, +1, !last);
}
/*----------------------------------------------------------------------------
MakeNewWindow
Create a new article window.
Entry: groupName = name of group, or nil if fetching by message id.
number = article number. Ignored if fetching by message id.
id = message id string, including < and > delimiters, or nil
if article was fetched by article number.
parent = pointer to parent subject window, or nil if opening by id.
checkTitle = true if window title should contain checkmark.
reuse = pointer to article window to be reused, or nil to
open new article window.
flagReqd = true if BinHex or uuencode text
must include special "begin" flag line.
txt = handle to article text.
length = length of article text.
attachedFile = true if article contains attached file.
Exit: function result = error code.
*theWindow = pointer to new article window.
----------------------------------------------------------------------------*/
static OSErr MakeNewWindow (char *groupName, long number,
char *id, WindowPtr parent, Boolean checkTitle, WindowPtr reuse,
Boolean flagReqd, Handle txt, long length, Boolean attachedFile,
WindowPtr *theWindow)
{
short panelHeight, width, height;
WindowPtr wind = nil;
TWindow **info;
Rect r;
TEHandle theTE;
ControlHandle vScroll;
Handle fullText;
long offset;
OSErr err = noErr;
Handle msgId, groupNameHandle;
CStr255 title;
short len;
char state;
GrafPtr port;
short zoomDir;
Rect arrowRect, backwardHotRect, forwardHotRect;
ArrowPairRef sectionArrowPair, nextPrevArrowPair;
MyICReadSharedPrefs(kICScreenFont);
GetPort(&port);
FindHeaderCString(txt, "Subject", title, sizeof(title));
c2pstr(title);
if (checkTitle) {
len = (unsigned char)title[0];
if (len == 255) len = 254;
BlockMoveData(title + 1, title + 2, len);
title[1] = checkMark;
title[0] = len + 1;
}
if (reuse == nil) {
err = CreateNewWindow(kArticle, (StringPtr)title,
gPrefs.textFont, gPrefs.textSize, &wind);
if (err != noErr) goto exit;
SetPort(wind);
info = (TWindow**)GetWRefCon(wind);
err = ComputePanelInfo(wind, txt);
if (err != noErr) goto exit;
panelHeight = (**info).panelHeight;
width = kMinWindowWidth;
height = MinHeight(wind);
RestoreLockedWindowPosition(wind, width, height, gPrefs.articleWindPosLocked,
&gPrefs.articleWindLocn);
width = wind->portRect.right;
height = wind->portRect.bottom;
SetRect(&r, width-15, panelHeight-1, width+1, height-14);
vScroll = NewControl(wind, &r, "\p", false, 0, 0, 0, scrollBarProc, 1);
(**info).vScroll = vScroll;
zoomDir = inZoomOut;
} else {
SaveArticleInCache(reuse);
wind = reuse;
SetPort(wind);
SetWTitle(wind, (StringPtr)title);
InvalRect(&wind->portRect);
info = (TWindow**)GetWRefCon(wind);
err = ComputePanelInfo(wind, txt);
if (err != noErr) goto exit;
panelHeight = (**info).panelHeight;
width = wind->portRect.right;
height = wind->portRect.bottom;
SetRect(&r, width-15, panelHeight-1, width+1, height-14);
vScroll = (**info).vScroll;
(**vScroll).contrlRect = r;
SetControlValue(vScroll, 0);
TEDispose((**info).theTE);
(**info).theTE = nil;
MyDisposeHandle((**info).sectionBreaks);
(**info).sectionBreaks = nil;
if ((**info).hScroll != nil) DisposeControl((**info).hScroll);
(**info).hScroll = nil;
DisposeArrowPair((**info).sectionArrowPair);
(**info).sectionArrowPair = nil;
DisposeArrowPair((**info).nextPrevArrowPair);
(**info).nextPrevArrowPair = nil;
RemoveChild(parent, wind);
(**info).maxBodyLineWidth = 0;
(**info).numBodyLines = 0;
zoomDir = zoomOutOnlyIfBigger;
}
GetTextRect(wind, &r);
theTE = TENew(&r, &r);
(**theTE).clickLoop = gAutoScrollUPP;
if (gHaveTEOutlineHilite) TEFeatureFlag(teFOutlineHilite, TEBitSet, theTE);
(**info).theTE = theTE;
(**info).parentWindow = parent;
(**info).fullText = fullText = txt;
txt = nil;
(**info).showDetails = gPrefs.showArtHeaders;
(**info).attachedFile = attachedFile;
if (groupName == nil) {
(**info).groupName = nil;
} else {
err = MyPtrToHand(groupName, &groupNameHandle, strlen(groupName) + 1);
if (err != noErr) goto exit;
(**info).groupName = groupNameHandle;
(**info).number = number;
}
(**info).flagReqd = flagReqd;
if (parent == nil) {
err = MyPtrToHand(id, &msgId, strlen(id)+1);
if (err != noErr) goto exit;
(**info).msgId = msgId;
} else {
err = AddChild(parent, wind);
if (err != noErr) goto exit;
}
err = CalcSectionBreaks(wind);
if (err != noErr) goto exit;
length = (*(**info).sectionBreaks)[1];
offset = gPrefs.showArtHeaders ? 0 : FindBody(fullText);
if (offset > length) offset = length;
state = MyHGetState(fullText);
MyHLock(fullText);
err = MyTESetText(*fullText+offset, length-offset, theTE);
MyHSetState(fullText, state);
if (err != noErr) goto exit;
TESetSelect(0, 0, theTE);
if ((**info).numSections > 1) {
SetRect(&arrowRect, kSectionMargin - 26, wind->portRect.bottom - 13,
kSectionMargin - 4, wind->portRect.bottom - 3);
SetRect(&backwardHotRect, kSectionMargin - 30, wind->portRect.bottom - 15,
kSectionMargin - 15, wind->portRect.bottom);
forwardHotRect = backwardHotRect;
OffsetRect(&forwardHotRect, 15, 0);
err = CreateArrowPair(false, &arrowRect, &backwardHotRect,
&forwardHotRect, §ionArrowPair);
if (err != noErr) goto exit;
EnableOrDisableArrowPair(sectionArrowPair, -1, false);
(**info).sectionArrowPair = sectionArrowPair;
}
if (parent != nil) {
SetRect(&arrowRect, wind->portRect.right - 17, 8, wind->portRect.right - 5, 30);
backwardHotRect = forwardHotRect = arrowRect;
backwardHotRect.bottom = forwardHotRect.top = 21;
backwardHotRect.top++;
forwardHotRect.bottom++;
err = CreateArrowPair(true, &arrowRect, &backwardHotRect,
&forwardHotRect, &nextPrevArrowPair);
if (err != noErr) goto exit;
(**info).nextPrevArrowPair = nextPrevArrowPair;
}
if (!(**info).windPosLocked) {
err = DoZoom(wind, zoomDir);
if (err != noErr) goto exit;
}
TEScrollAdjustScrollMax(theTE, (**info).vScroll);
if (reuse != nil) {
if ((**info).hScroll != nil) ShowControl((**info).hScroll);
TEActivate(theTE);
}
*theWindow = wind;
SetPort(port);
return noErr;
exit:
MyDisposeHandle(txt);
DoClose(wind);
SetPort(port);
return err;
}
/*----------------------------------------------------------------------------
GetArticleAndMakeNewWindow
Get an article from the news server and create a new article window.
Entry: host = address of news server, or nil to use default server.
port = port number on news server if host not nil.
groupName = name of group, or nil if fetching by message id.
number = article number. Ignored if fetching by message id.
id = message id string, including < and > delimiters. Ignored
if fetching by article number.
parent = pointer to parent subject window, or nil if opening by id.
checkTitle = true if window title should contain checkmark.
reuse = pointer to article window to be reused, or nil to
open new article window.
flagReqd = true if BinHex or uuencode text
must include special "begin" flag line.
Exit: function result = error code.
*theWindow = pointer to new article window, or nil if article
does not exist on server.
----------------------------------------------------------------------------*/
static OSErr GetArticleAndMakeNewWindow (char *host, short port,
char *groupName, long number, char *id,
WindowPtr parent, Boolean checkTitle, WindowPtr reuse,
Boolean flagReqd, WindowPtr *theWindow)
{
Handle txt = nil;
long length;
OSErr err = noErr;
Boolean attachedFile;
if (host != nil || !GetArticleFromCache(groupName, number, id,
flagReqd, &txt, &length, &attachedFile))
{
err = GetArticle(host, port, groupName, number, id, "ARTICLE", true,
flagReqd, &txt, &length, &attachedFile);
if (err != noErr) return err;
if (txt == nil) {
*theWindow = nil;
return noErr;
}
}
return MakeNewWindow(groupName, number, id, parent, checkTitle, reuse,
flagReqd, txt, length, attachedFile, theWindow);
}
/*----------------------------------------------------------------------------
CheckAlreadyOpen
Check to see if a referenced article window is already open. If it is,
bring it to the front.
Entry: msgId = C-format message id of article.
Exit: function result = true if window already open.
----------------------------------------------------------------------------*/
static Boolean CheckAlreadyOpen (char *msgId)
{
WindowPtr wind;
TWindow **info;
TWindowKind kind;
wind = FrontWindow();
while (wind != nil) {
kind = GetMyWindowKind(wind);
if (kind == kArticle) {
info = (TWindow**)GetWRefCon(wind);
if ((**info).msgId != nil) {
if (strcmp(msgId, *(**info).msgId) == 0) {
MySelectWindow(wind);
return true;
}
}
}
wind = (WindowPtr)(((WindowPeek)wind)->nextWindow);
}
return false;
}
/*----------------------------------------------------------------------------
OpenSubjectItem
Open an article window corresponding to an item in a subject window.
Entry: wind = pointer to subject window.
item = the item in the subject window to open.
threadOrdinal = ordinal of article within thread to open if
theCell is a collapsed thread, else 1.
reuse = pointer to article window to be reused, or nil to
open new article window.
Exit: function result = error code.
*child = pointer to opened article window, or nil if
article does not exist on server.
----------------------------------------------------------------------------*/
OSErr OpenSubjectItem (WindowPtr wind, long item, long threadOrdinal,
WindowPtr reuse, WindowPtr *child)
{
WindowPtr newWind = nil;
TWindow **info, **newInfo;
BigListRef subjectList;
TSubject **subjectArray;
CStr255 groupName;
long number;
OSErr err = noErr;
Boolean flagReqd;
long index;
info = (TWindow**) GetWRefCon(wind);
subjectList = (**info).subjectList;
subjectArray = (**info).subjectArray;
index = BigLGetData(subjectList, item);
while (threadOrdinal-- > 1)
index = (*subjectArray)[index].nextInThread;
if ((newWind = FindChildByIndex(wind, index)) != nil) {
MySelectWindow(newWind);
*child = newWind;
return noErr;
}
number = (*subjectArray)[index].number;
flagReqd = EncodedTextBeginLineIsRequired(subjectArray, index);
strcpy(groupName, *gGroupNames + (**info).groupNameOffset);
err = GetArticleAndMakeNewWindow(nil, 0, groupName, number, nil, wind, true, reuse,
flagReqd, &newWind);
if (err != noErr) goto exit;
if (newWind != nil) {
newInfo = (TWindow**)GetWRefCon(newWind);
(**newInfo).parentSubject = index;
err = MarkArticle(newWind, true);
if (err != noErr) goto exit;
EnableOrDisableNextPrevArrows(newWind);
if (reuse == nil) MyShowWindow(newWind);
}
*child = newWind;
return noErr;
exit:
DoClose(newWind);
return err;
}
/*----------------------------------------------------------------------------
OpenReferencedArticle
Open a referenced article.
Entry: obj = message id or news URL of referenced article.
Exit: function result = error code (fnfErr if article not found).
----------------------------------------------------------------------------*/
OSErr OpenReferencedArticle (CStr255 obj)
{
CStr255 msgId;
WindowPtr newWind = nil;
OSErr err = noErr;
if (MyStrNEqual(obj, "news:", 5)) {
sprintf(msgId, "<%s>", obj+5);
} else if (MyStrNEqual(obj, "<news:", 6)) {
sprintf(msgId, "<%s", obj+6);
} else {
strcpy(msgId, obj);
}
if (CheckAlreadyOpen(msgId)) return noErr;
err = GetArticleAndMakeNewWindow(nil, 0, nil, 0, msgId, nil, false, nil, true, &newWind);
if (err != noErr) return err;
if (newWind == nil) {
return fnfErr;
} else {
MyShowWindow(newWind);
return noErr;
}
}
/*----------------------------------------------------------------------------
OpenArticleOnAnyServer
Open an article on any server, perhaps a different server from the
default one (for opening nntp URLs).
Entry: host = address of news server.
port = port number.
newsgroup = newsgroup name.
artNumber = article number.
Exit: function result = error code (fnfErr if article not found).
----------------------------------------------------------------------------*/
OSErr OpenArticleOnAnyServer (char *host, short port,
char *newsgroup, long artNumber)
{
WindowPtr newWind;
OSErr err = noErr;
err = GetArticleAndMakeNewWindow(host, port, newsgroup, artNumber,
nil, nil, false, nil, true, &newWind);
if (err != noErr) return err;
if (newWind == nil) {
return fnfErr;
} else {
MyShowWindow(newWind);
return noErr;
}
}
/*----------------------------------------------------------------------------
FormFileName
Form the default file name for saving an article.
Entry: wind = pointer to article window.
Exit: fileName = default file name.
----------------------------------------------------------------------------*/
static void FormFileName (WindowPtr wind, Str31 fileName)
{
Str255 title;
GetWTitle(wind, title);
if (title[0] > 0 && title[1] == checkMark) {
title[0]--;
BlockMoveData(title+2, title+1, title[0]);
}
MakeLegalFileName(title, fileName);
}
/*----------------------------------------------------------------------------
PresentStandardArticleSaveFileDialog
Present the standard save file dialog.
Entry: fileName = default file name.
*saveEncodedText = default value for save encoded text option.
*saveThreadsToSeparateFiles = default value for save threads
to separate files option.
Exit: function result = error code.
*fSpec = file spec.
*scriptTag = script code.
*saveEncodedText = true to save encoded text.
*saveThreadsToSeparateFiles = true to save threads to separate
files.
----------------------------------------------------------------------------*/
OSErr PresentStandardArticleSaveFileDialog (Str31 fileName, FSSpec *fSpec,
ScriptCode *scriptTag, Boolean *saveEncodedText,
Boolean *saveThreadsToSeparateFiles)
{
StandardFileReply reply;
Str255 prompt;
GetPString(kStrSaveArticlePrompt, prompt);
MyStandardPutArticle(prompt, fileName, &reply,
saveEncodedText, saveThreadsToSeparateFiles,
gPrefs.savedArtDefaultFolder ? gPrefs.savedArtDefaultFolderAlias : nil);
if (!reply.sfGood) return userCanceledErr;
*fSpec = reply.sfFile;
*scriptTag = reply.sfScript;
return noErr;
}
/*----------------------------------------------------------------------------
PresentStandardArticleGetFileDialog
Present the standard get file dialog.
Entry: *saveEncodedText = default value of save encoded text option.
Exit: function result = error code.
*fSpec = file spec.
*saveEncodedText = true to save encoded text.
----------------------------------------------------------------------------*/
OSErr PresentStandardArticleGetFileDialog (FSSpec *fSpec, Boolean *saveEncodedText)
{
StandardFileReply reply;
MyStandardAppendArticle(nil, 1, "TEXT", &reply, saveEncodedText,
gPrefs.savedArtDefaultFolder ? gPrefs.savedArtDefaultFolderAlias : nil);
if (!reply.sfGood) return userCanceledErr;
*fSpec = reply.sfFile;
return noErr;
}
/*----------------------------------------------------------------------------
SaveTextToArticleFile
Save text to a file.
Entry: text = handle to text to be saved.
start = offset in text of first char to save.
end = offset in text following last char char to save.
fSpec = pointer to file spec.
scriptTag = script code.
append = true to append text to end of file.
Exit: function result = error code.
----------------------------------------------------------------------------*/
static OSErr SaveTextToArticleFile (Handle text, long start, long end,
FSSpec *fSpec, ScriptCode scriptTag, Boolean append)
{
OSErr err = noErr;
long length;
short refNum = 0;
char state;
char *separator, *p;
Boolean needCR, empty;
state = MyHGetState(text);
err = OpenDataForkWriteCreateIfMissing(fSpec, gPrefs.savedArtCreator, 'TEXT',
scriptTag, append, &refNum, &empty);
if (err != noErr) goto exit;
if (append && !empty) {
separator =
"\r----------------------------------------------------------------------\r\r";
length = strlen(separator);
err = MyFSWriteNoCache(refNum, &length, separator, GiveTime);
if (err != noErr) goto exit;
}
length = end - start;
MyHLock(text);
for (p = *text + end - 1; p >= *text + start && *p == CR; p--) /* do nothing */;
p++;
if (p < *text + end) {
needCR = false;
length = p + 1 - *text - start;
} else {
needCR = true;
}
err = MyFSWriteNoCache(refNum, &length, *text + start, GiveTime);
if (err != noErr) goto exit;
if (needCR) {
length = 1;
err = MyFSWriteNoCache(refNum, &length, CRSTR, GiveTime);
if (err != noErr) goto exit;
}
MyHSetState(text, state);
MyFSClose(refNum, GiveTime);
return noErr;
exit:
MyHSetState(text, state);
if (refNum != 0) MyFSClose(refNum, GiveTime);
return err;
}
/*----------------------------------------------------------------------------
GetAndSaveFullTextToArticleFile
Get the full text of an article with attached binaries and save it
to a file.
Entry: wind = pointer to article window.
fSpec = pointer to file spec.
scriptTag = script code.
append = true to append text to end of file.
Exit: function result = error code.
----------------------------------------------------------------------------*/
static OSErr GetAndSaveFullTextToArticleFile (WindowPtr wind,
FSSpec *fSpec, ScriptCode scriptTag, Boolean append)
{
OSErr err = noErr;
long length;
short refNum = 0;
char *separator;
Boolean empty;
TWindow **info, **parentInfo;
WindowPtr parent;
TSubject **subjectArray;
short index;
CStr255 groupName;
long number;
TAttachedFileKind partKind;
Handle msgId;
char state;
err = DisplayStatusMessageNumber(kStrGettingAndSavingArticle);
if (err != noErr) return err;
err = OpenDataForkWriteCreateIfMissing(fSpec, gPrefs.savedArtCreator, 'TEXT',
scriptTag, append, &refNum, &empty);
if (err != noErr) goto exit;
if (append && !empty) {
separator =
"\r----------------------------------------------------------------------\r\r";
length = strlen(separator);
err = MyFSWriteNoCache(refNum, &length, separator, GiveTime);
if (err != noErr) goto exit;
}
info = (TWindow**)GetWRefCon(wind);
parent = (**info).parentWindow;
if (parent == nil) {
msgId = (**info).msgId;
state = MyHGetState(msgId);
MyHLockHi(msgId);
err = CopyArticleToFile(nil, 0, *msgId, "ARTICLE", refNum,
false, false, &partKind);
MyHSetState(msgId, state);
if (err != noErr) goto exit;
} else {
parentInfo = (TWindow**)GetWRefCon(parent);
subjectArray = (**parentInfo).subjectArray;
index = (**info).parentSubject;
strcpy(groupName, *gGroupNames + (**parentInfo).groupNameOffset);
number = (*subjectArray)[index].number;
err = CopyArticleToFile(groupName, number, nil, "ARTICLE", refNum,
false, false, &partKind);
if (err != noErr) goto exit;
}
MyFSClose(refNum, GiveTime);
return noErr;
exit:
if (refNum != 0) MyFSClose(refNum, GiveTime);
return err;
}
/*----------------------------------------------------------------------------
CheckArticleFileExists
Check to see if an article file already exists in the default article
file save directory. If it does, present an alert asking the user to
append, replace, cancel, or pick a new name. If the "Append if file
alread exists" preference is turned on, automatically append.
Entry: fileName = file name.
*saveEncodedText = default value for save encoded text
option.
*saveThreadsToSeparateFiles = default value for save threads
to separate files option.
Exit: function result = error code.
*fSpec = file spec.
*scriptTag = script code.
*append = true to append.
*saveEncodedText = true to save encoded text.
*saveThreadsToSeparateFiles = true to save threads to
separate files.
----------------------------------------------------------------------------*/
OSErr CheckArticleFileExists (Str31 fileName, FSSpec *fSpec,
ScriptCode *scriptTag, Boolean *append,
Boolean *saveEncodedText, Boolean *saveThreadsToSeparateFiles)
{
OSErr err = noErr;
DialogPtr dlg = nil;
Boolean valid;
short item;
*append = false;
ValidateSavedFolderAlias(gPrefs.savedArtDefaultFolderAlias,
&fSpec->vRefNum, &fSpec->parID, &valid);
if (!valid) {
ErrorMessageNumber(kStrDefaultArtFoldNotFound);
err = PresentStandardArticleSaveFileDialog(fileName, fSpec, scriptTag,
saveEncodedText, saveThreadsToSeparateFiles);
if (err != noErr) return err;
return noErr;
}
*scriptTag = smSystemScript;
CopyPascalString(fSpec->name, fileName);
*append = gPrefs.appendIfFileAlreadyExists;
err = FileOrFolderExists(fSpec);
if (err == fnfErr) return noErr;
if (err != noErr) return err;
if (gPrefs.appendIfFileAlreadyExists) return noErr;
*append = false;
err = MyGetNewDialog(kArticleFileExistsAlert, 1, 2, &dlg);
if (err != noErr) return err;
ParamText(fileName, "\p", "\p", "\p");
SetItemKeyEquivalent(dlg, 3, 'R');
SetItemKeyEquivalent(dlg, 4, 'A');
SetItemKeyEquivalent(dlg, 5, 'D');
SysBeep(0);
MyModalDialog(dlg, gDialogFilterUPP, &item);
if (item == 5) DlgFlashButton(dlg, 4);
err = DoClose(dlg);
if (err != noErr) return err;
switch (item) {
case 1: /* Pick a New Name */
err = PresentStandardArticleSaveFileDialog(fileName, fSpec,
scriptTag, saveEncodedText, saveThreadsToSeparateFiles);
if (err != noErr) return err;
return noErr;
case 2: /* Cancel */
return userCanceledErr;
case 3: /* Replace */
return noErr;
case 4: /* Append */
case 5: /* Append (off screen, Cmd-D equiv.) */
*append = true;
return noErr;
}
return noErr;
}
/*----------------------------------------------------------------------------
SaveArticleWindToFile
Save article window text to a file.
Entry: wind = pointer to article window.
modifiers = modifiers field from event record.
fSpec = pointer to file spec.
scriptTag = script code.
append = true to append text to end of file.
saveEncodedText = true to save encoded text.
Exit: function result = error code.
----------------------------------------------------------------------------*/
static OSErr SaveArticleWindToFile (WindowPtr wind, short modifiers,
FSSpec *fSpec, ScriptCode scriptTag, Boolean append,
Boolean saveEncodedText)
{
TWindow **info;
long start, end;
Handle text;
TEHandle theTE;
Boolean shift;
MyICReadSharedPrefs(kICeditorHelper);
info = (TWindow**)GetWRefCon(wind);
shift = (modifiers & shiftKey) != 0;
if (!shift && saveEncodedText && (**info).attachedFile) {
return GetAndSaveFullTextToArticleFile(wind, fSpec, scriptTag, append);
} else {
if (shift) {
theTE = (**info).theTE;
text = (**theTE).hText;
start = (**theTE).selStart;
end = (**theTE).selEnd;
} else {
text = (**info).fullText;
start = 0;
end = MyGetHandleSize(text);
}
return SaveTextToArticleFile(text, start, end, fSpec, scriptTag, append);
}
}
/*----------------------------------------------------------------------------
CheckForMissingParts
Check for missing parts before trying to extract binaries for an article
window.
Entry: wind = pointer to article window.
Exit: function result = error code.
----------------------------------------------------------------------------*/
static OSErr CheckForMissingParts (WindowPtr wind)
{
TWindow **info, **parentInfo;
WindowPtr parent;
TSubject **subjectArray;
short index;
info = (TWindow**)GetWRefCon(wind);
parent = (**info).parentWindow;
parentInfo = (TWindow**)GetWRefCon(parent);
subjectArray = (**parentInfo).subjectArray;
index = (**info).parentSubject;
if ((*subjectArray)[index].incomplete) {
NoteMessageNumber(kStrDecodePartsMissing);
return userCanceledErr;
}
return noErr;
}
/*----------------------------------------------------------------------------
PresentStandardDownloadSaveFileDialog
Present the standard file dialog for the temp file for extracting
binaries.
Entry: fileName = default file name.
Exit: function result = error code.
*fSpec = file spec.
*scriptTag = script code.
----------------------------------------------------------------------------*/
OSErr PresentStandardDownloadSaveFileDialog (Str31 fileName, FSSpec *fSpec,
ScriptCode *scriptTag)
{
StandardFileReply reply;
Str255 prompt;
MyICReadSharedPrefs(kICDownloadFolder);
GetPString(kStrDownloadTempFilePrompt, prompt);
MyStandardPutFile(prompt, fileName, &reply,
gPrefs.savedBinDefaultFolder ? gPrefs.savedBinDefaultFolderAlias : nil);
if (!reply.sfGood) return userCanceledErr;
*fSpec = reply.sfFile;
*scriptTag = reply.sfScript;
return noErr;
}
/*----------------------------------------------------------------------------
CheckDownloadFileExists
Check to see if a temp file already exists in the download directory.
If it does, make the temp file name unique.
Entry: fileName = default file name.
Exit: function result = error code.
*fSpec = file spec.
*scriptTag = script code.
----------------------------------------------------------------------------*/
OSErr CheckDownloadFileExists (Str31 fileName, FSSpec *fSpec,
ScriptCode *scriptTag)
{
OSErr err = noErr;
Boolean valid;
MyICReadSharedPrefs(kICDownloadFolder);
ValidateSavedFolderAlias(gPrefs.savedBinDefaultFolderAlias,
&fSpec->vRefNum, &fSpec->parID, &valid);
if (!valid) {
ErrorMessageNumber(kStrDownloadFoldNotFound);
err = PresentStandardDownloadSaveFileDialog(fileName, fSpec, scriptTag);
if (err != noErr) return err;
return noErr;
}
*scriptTag = smSystemScript;
CopyPascalString(fSpec->name, fileName);
return MakeFileNameUnique(fSpec, nil);
}
/*----------------------------------------------------------------------------
SaveBinariesToFile
Save binaries to a file for an article.
Entry: wind = pointer to subject window.
index = index in subject array of article.
fSpec = pointer to file spec.
scriptTag = script code.
artNum = article number being saved (1,2,3,...numArts).
numArts = total number of articles being saved.
Exit: function result = error code.
*fileKind = kind of attached file.
----------------------------------------------------------------------------*/
static OSErr SaveBinariesToFile (WindowPtr wind, long index, FSSpec *fSpec,
ScriptCode scriptTag, long artNum, long numArts,
TAttachedFileKind *fileKind)
{
TWindow **info;
TSubject **subjectArray;
long numPartsToSave;
CStr255 statusMsg, statusMsgFormat;
Boolean append = false;
long partNum, nextIndex, threadHeadIndex, n;
Handle text = nil;
long number;
CStr255 groupName;
Boolean empty, hasParts;
OSErr err = noErr;
short refNum;
TAttachedFileKind partKind;
long item;
MyICReadSharedPrefs(kICeditorHelper);
info = (TWindow**)GetWRefCon(wind);
subjectArray = (**info).subjectArray;
strcpy(groupName, *gGroupNames + (**info).groupNameOffset);
*fileKind = kNoAttachedFile;
threadHeadIndex = (*subjectArray)[index].threadHeadIndex;
if ((*subjectArray)[index].numParts == kMaxLong) {
hasParts = false;
numPartsToSave = 1;
partNum = 1;
} else {
hasParts = true;
numPartsToSave = (*subjectArray)[index].numParts;
index = threadHeadIndex;
}
if (numArts > 1) {
GetCString(kStrGettingAndSavingArtAofBPartNofM, statusMsgFormat);
} else if (hasParts) {
GetCString(kStrGettingAndSavingPartNofM, statusMsgFormat);
} else {
GetCString(kStrGettingAndSavingFile, statusMsg);
}
err = OpenDataForkWriteCreateIfMissing(fSpec, gPrefs.savedArtCreator, 'TEXT',
scriptTag, false, &refNum, &empty);
if (err != noErr) return err;
while (true) {
if (hasParts) {
while (index != -1 && (*subjectArray)[index].partNum == kMaxLong)
index = (*subjectArray)[index].nextInThread;
if (index == -1) break;
partNum = (*subjectArray)[index].partNum;
while (true) {
nextIndex = (*subjectArray)[index].nextInThread;
while (nextIndex != -1 && (*subjectArray)[nextIndex].partNum == kMaxLong)
nextIndex = (*subjectArray)[nextIndex].nextInThread;
if (nextIndex == -1) break;
if ((*subjectArray)[nextIndex].partNum != partNum) break;
index = nextIndex;
}
}
if (index == -1) break;
if (numArts > 1) {
sprintf(statusMsg, statusMsgFormat, artNum, numArts, partNum, numPartsToSave);
} else if (hasParts) {
sprintf(statusMsg, statusMsgFormat, partNum, numPartsToSave);
}
err = DisplayStatusMessage(statusMsg);
if (err != noErr) goto exit;
number = (*subjectArray)[index].number;
err = CopyArticleToFile(groupName, number, nil, "ARTICLE", refNum,
false, false, &partKind);
if (err != noErr) goto exit;
if (partKind == kArtNotOnServer) {
*fileKind = kArtNotOnServer;
goto exit;
} else if (*fileKind == kNoAttachedFile) {
*fileKind = partKind;
}
if (!hasParts) break;
index = (*subjectArray)[index].nextInThread;
}
if (*fileKind != kArtNotOnServer && *fileKind != kNoAttachedFile) {
item = FindParentItem(wind, threadHeadIndex);
if (item >= 0) {
MarkSubjectItemRead(wind, item);
if (!(*subjectArray)[threadHeadIndex].collapsed) {
n = (*subjectArray)[threadHeadIndex].threadLength;
while (--n > 0) {
item++;
MarkSubjectItemRead(wind, item);
}
}
}
}
exit:
MyFSClose(refNum, GiveTime);
return err;
}
/*----------------------------------------------------------------------------
RunDecodeHelperProgram
Run the helper program to decode the temp file.
Entry: fSpec = pointer to file spec for temp file.
fileKind = kind of attached binary.
Exit: function result = error code.
----------------------------------------------------------------------------*/
OSErr RunDecodeHelperProgram (FSSpec *fSpec, TAttachedFileKind fileKind)
{
OSErr err = noErr;
FSSpec appSpec, fSpecWithSuffix;
Boolean running;
ProcessSerialNumber psn;
CStr255 fmt, msg;
char *suffix;
StringPtr helperName;
helperName = fileKind == kBinHex ? gPrefs.hqxHelperName : gPrefs.uuHelperName;
err = FindAppFromSig(fileKind == kBinHex ? gPrefs.hqxHelper : gPrefs.uuHelper,
&appSpec, &running, &psn);
if (err != noErr) goto exit1;
fSpecWithSuffix = *fSpec;
suffix = fileKind == kBinHex ? ".hqx" : ".uu";
err = MakeFileNameUnique(&fSpecWithSuffix, suffix);
if (err != noErr) goto exit3;
err = FSpRename(fSpec, fSpecWithSuffix.name);
if (err != noErr) goto exit3;
err = LaunchAppWithDoc(running, &appSpec, &psn, &fSpecWithSuffix, 0,
launchContinue | launchNoFileFlags | launchDontSwitch | launchUseMinimum);
if (err != noErr) goto exit2;
return noErr;
exit1:
if (err == fnfErr) {
GetCString(kStrCantFindDecodeHelper, fmt);
p2cstr(helperName);
sprintf(msg, fmt, helperName);
c2pstr((char*)helperName);
DoCantFindHelperDialog(msg);
return userCanceledErr;
} else {
goto exit3;
}
exit2:
if (err == memFullErr || err == memFragErr || err == appMemFullErr) {
GetCString(kStrDecodeHelperNoMem, fmt);
p2cstr(helperName);
sprintf(msg, fmt, helperName);
c2pstr((char*)helperName);
ErrorMessage(msg);
return userCanceledErr;
} else {
goto exit3;
}
exit3:
GetCString(kStrDecodeHelperUnexpectedErr, fmt);
p2cstr(helperName);
sprintf(msg, fmt, err, helperName);
c2pstr((char*)helperName);
ErrorMessage(msg);
return userCanceledErr;
}
/*----------------------------------------------------------------------------
ExtractBinaries
Extract binaries for an article or thread.
Entry: wind = pointer to subject window.
index = index of article or part in subject array.
artNum = article number being saved (1,2,3,...numArts).
numArts = total number of articles being saved.
modifiers = modifiers field from event record.
Exit: function result = error code.
*fileKind = kind of attached file.
----------------------------------------------------------------------------*/
OSErr ExtractBinaries (WindowPtr wind, short index, short artNum,
short numArts, short modifiers, TAttachedFileKind *fileKind)
{
static Str31 fileName;
static FSSpec fSpec;
static ScriptCode scriptTag;
OSErr err = noErr;
MyICReadSharedPrefs(kICeditorHelper);
if (artNum == 1) {
GetPString(kStrTempFileName, fileName);
if (gPrefs.savedBinDefaultFolder && (modifiers & optionKey) == 0) {
err = CheckDownloadFileExists(fileName, &fSpec, &scriptTag);
if (err != noErr) return err;
} else {
err = PresentStandardDownloadSaveFileDialog(fileName, &fSpec, &scriptTag);
if (err != noErr) return err;
}
} else {
CopyPascalString(fSpec.name, fileName);
err = MakeFileNameUnique(&fSpec, nil);
if (err != noErr) return err;
}
err = SaveBinariesToFile(wind, index, &fSpec, scriptTag, artNum, numArts,
fileKind);
if (err != noErr) goto exit;
if (*fileKind == kNoAttachedFile || *fileKind == kArtNotOnServer) {
FSpDelete(&fSpec);
return noErr;
}
err = RunDecodeHelperProgram(&fSpec, *fileKind);
if (err != noErr) return err;
return noErr;
exit:
FSpDelete(&fSpec);
return err;
}
/*----------------------------------------------------------------------------
TooStupidAlert
Present the "I'm too stupid to extract binaries" alert.
Exit: function result = error code (userCancledErr if alert
presented with no problems).
----------------------------------------------------------------------------*/
static OSErr TooStupidAlert (void)
{
OSErr err = noErr;
DialogPtr dlg;
short item;
err = MyGetNewDialog(kTooStupidAlert, ok, 0, &dlg);
if (err != noErr) return err;
SysBeep(0);
MyModalDialog(dlg, gDialogFilterUPP, &item);
err = DoClose(dlg);
if (err != noErr) return err;
return userCanceledErr;
}
/*----------------------------------------------------------------------------
ExtractBinariesForArticleWindow
Extract binaries for an article window.
Entry: wind = pointer to article window.
modifiers = modifiers field from event record.
Exit: function result = error code.
----------------------------------------------------------------------------*/
static OSErr ExtractBinariesForArticleWindow (WindowPtr wind, short modifiers)
{
TWindow **info;
OSErr err = noErr;
TAttachedFileKind fileKind;
info = (TWindow**)GetWRefCon(wind);
if ((**info).parentWindow == nil) return TooStupidAlert();
err = CheckForMissingParts(wind);
if (err != noErr) return err;
err = ExtractBinaries((**info).parentWindow, (**info).parentSubject,
1, 1, modifiers, &fileKind);
if (err != noErr) return err;
if (fileKind == kNoAttachedFile) {
NoteMessageNumber(kStrArtHasNoAttachedFile);
return userCanceledErr;
}
if (fileKind == kArtNotOnServer) {
NoteMessageNumber(kStrArtNotOnServer);
return userCanceledErr;
}
return noErr;
}
/*----------------------------------------------------------------------------
ExtractBinariesDragAndDrop
Extract binaries for an article window and the end of a drag
and drop sequence.
Entry: wind = pointer to article window.
fSpec = pointer to file spec.
Exit: function result = error code.
----------------------------------------------------------------------------*/
static OSErr ExtractBinariesDragAndDrop (WindowPtr wind, FSSpec *fSpec)
{
TWindow **info;
OSErr err = noErr;
TAttachedFileKind fileKind;
info = (TWindow**)GetWRefCon(wind);
err = CheckForMissingParts(wind);
if (err != noErr) goto exit;
err = SaveBinariesToFile((**info).parentWindow, (**info).parentSubject,
fSpec, smSystemScript, 1, 1, &fileKind);
if (err != noErr) goto exit;
if (fileKind == kNoAttachedFile) {
NoteMessageNumber(kStrArtHasNoAttachedFile);
err = userCanceledErr;
goto exit;
}
if (fileKind == kArtNotOnServer) {
NoteMessageNumber(kStrArtNotOnServer);
return userCanceledErr;
}
err = RunDecodeHelperProgram(fSpec, fileKind);
if (err != noErr) return err;
return noErr;
exit:
FSpDelete(fSpec);
return err;
}
/*----------------------------------------------------------------------------
CanExtractBinaries
Check to see if we can extract binaries from an article window (it has an
attached binary file, or it is part 0 of a multiple-part posting).
Entry: wind = pointer to article window.
Exit: function result = true if can extract binaries from this window.
*iconRect = rect enclosing attached file icon.
----------------------------------------------------------------------------*/
static Boolean CanExtractBinaries (WindowPtr wind, Rect *iconRect)
{
WindowPtr parent;
TWindow **info, **parentInfo;
TSubject **subjectArray;
Boolean result;
info = (TWindow**)GetWRefCon(wind);
parent = (**info).parentWindow;
if ((**info).attachedFile) {
result = true;
} else {
if (parent == nil) {
result = false;
} else {
parentInfo = (TWindow**)GetWRefCon(parent);
subjectArray = (**parentInfo).subjectArray;
result = (*subjectArray)[(**info).parentSubject].partNum == 0;
}
}
if (result) {
if (parent == nil) {
SetRect(iconRect, wind->portRect.right - 32, 0,
wind->portRect.right, 32);
} else {
SetRect(iconRect, wind->portRect.right - 44, 0,
wind->portRect.right - 12, 32);
}
}
return result;
}
/*----------------------------------------------------------------------------
DragAttachedFileIconSendProc
The Drag Manager send proc for dragging the attached file icon.
Entry: theType = flavor type = 'SPEC'.
dragSendRefCon = pointer to file spec, with file name
filled in.
theItemRef = item reference.
theDragRef = drag reference.
Exit: function result = error code.
file spec volume reference number and directory id filled in.
file created.
file spec sent to Finder as flavor data.
----------------------------------------------------------------------------*/
static pascal OSErr DragAttachedFileIconSendProc (FlavorType theType,
void *dragSendRefCon, ItemReference theItemRef, DragReference theDragRef)
{
AEDesc dropLocation;
OSErr err = noErr;
FSSpec *fSpec;
if (DragTargetWasTrash(theDragRef)) return userCanceledErr;
MyICReadSharedPrefs(kICeditorHelper);
fSpec = (FSSpec*)dragSendRefCon;
err = GetDropLocation(theDragRef, &dropLocation);
if (err != noErr) return err;
err = GetDropLocationDirectory(&dropLocation, &fSpec->vRefNum, &fSpec->parID);
if (err != noErr) goto exit;
err = MakeFileNameUnique(fSpec, nil);
if (err != noErr) goto exit;
err = FSpCreate(fSpec, gPrefs.savedArtCreator, 'TEXT', smSystemScript);
if (err != noErr) goto exit;
err = SetDragItemFlavorData(theDragRef, theItemRef, 'SPEC',
fSpec, sizeof(FSSpec), 0);
exit:
AEDisposeDesc(&dropLocation);
return err;
}
/*----------------------------------------------------------------------------
DragAttachedFileIcon
Handle a mouse down event in the attached file icon of an article
window when the Drag Manager is present.
Entry: wind = pointer to article window.
iconRect = pointer to icon rectangle in local coords.
modifiers = modifiers field from event record.
Exit: function result = error code.
----------------------------------------------------------------------------*/
static OSErr DragAttachedFileIcon (WindowPtr wind, Rect *iconRect, short modifiers)
{
OSErr err = noErr;
DragReference dragRef;
Boolean haveDragRef = false;
PromiseHFSFlavor promise;
RgnHandle dragRgn = nil;
Rect globalIconRect;
FSSpec fSpec;
PlotIconID(iconRect, 0, ttSelected, kFileIconID);
if (WaitMouseMoved(gCurEvent.where)) {
err = NewDrag(&dragRef);
if (err != noErr) goto exit;
MyICReadSharedPrefs(kICeditorHelper);
haveDragRef = true;
promise.fileType = 'TEXT';
promise.fileCreator = gPrefs.savedArtCreator;
promise.fdFlags = 0;
promise.promisedFlavor = 'SPEC';
err = AddDragItemFlavor(dragRef, 1, flavorTypePromiseHFS, &promise,
sizeof(PromiseHFSFlavor), 0);
if (err != noErr) goto exit;
err = AddDragItemFlavor(dragRef, 1, 'SPEC', nil, 0, 0);
if (err != noErr) goto exit;
dragRgn = NewRgn();
globalIconRect = *iconRect;
LocalToGlobalRect(&globalIconRect);
err = IconIDToRgn(dragRgn, &globalIconRect, ttNone, kFileIconID);
if (err != noErr) goto exit;
OutlineRegion(dragRgn);
GetPString(kStrTempFileName, fSpec.name);
err = SetDragSendProc(dragRef, gDragAttachedFileIconSendProcUPP, &fSpec);
if (err != noErr) goto exit;
err = TrackDrag(dragRef, &gCurEvent, dragRgn);
if (err != noErr) goto exit;
DisposeRgn(dragRgn);
DisposeDrag(dragRef);
PlotIconID(iconRect, 0, ttNone, kFileIconID);
err = ExtractBinariesDragAndDrop(wind, &fSpec);
if (err != noErr) goto exit;
} else {
PlotIconID(iconRect, 0, ttNone, kFileIconID);
return ExtractBinariesForArticleWindow(wind, modifiers);
}
return noErr;
exit:
if (haveDragRef) DisposeDrag(dragRef);
if (dragRgn != nil) DisposeRgn(dragRgn);
PlotIconID(iconRect, 0, ttNone, kFileIconID);
return err;
}
/*----------------------------------------------------------------------------
Find
Search an article window for a pattern.
Entry: wind = pointer to article window.
offset = offset into currently displayed text section
to begin search, or -1 to start search at beginning.
gFindPattern = pattern.
Exit: function result = error code.
----------------------------------------------------------------------------*/
static OSErr Find (WindowPtr wind, long offset)
{
TWindow **info;
Handle fullText;
TEHandle theTE;
short curSection, numSections, newSection;
long **breaks;
char state;
long matchOffset, len;
OSErr err = noErr;
info = (TWindow**)GetWRefCon(wind);
fullText = (**info).fullText;
curSection = (**info).curSection;
numSections = (**info).numSections;
breaks = (**info).sectionBreaks;
if (offset == -1) {
offset = 0;
if (!(**info).showDetails) offset += FindBody(fullText);
} else {
if (curSection == 0 && !(**info).showDetails)
offset += FindBody(fullText);
offset += (*breaks)[curSection];
}
len = (*breaks)[numSections] - offset;
state = MyHGetState(fullText);
MyHLock(fullText);
err = MyNSubstringSearch(*fullText + offset, gFindPattern, len,
&matchOffset, GiveTime);
MyHSetState(fullText, state);
if (err != noErr) return err;
if (matchOffset == -1) {
SysBeep(0);
} else {
offset += matchOffset;
newSection = 0;
while (offset >= (*breaks)[newSection+1]) newSection++;
offset -= (*breaks)[newSection];
if (newSection != curSection) {
SetControlValue((**info).hScroll, newSection);
ScrollSection(wind, curSection - newSection);
}
if (newSection == 0 && !(**info).showDetails)
offset -= FindBody(fullText);
theTE = (**info).theTE;
TESetSelect(offset, offset + strlen(gFindPattern), theTE);
TEScrollScrollToMiddle(theTE, offset, (**info).vScroll);
}
return noErr;
}
/*----------------------------------------------------------------------------
DoSave
Handle the "Save" command.
Entry: wind = pointer to article window.
modifiers = modifiers field from event record.
Exit: function result = error code.
----------------------------------------------------------------------------*/
static OSErr DoSave (WindowPtr wind, short modifiers)
{
Str31 fileName;
FSSpec fSpec;
ScriptCode scriptTag;
Boolean append = false;
OSErr err = noErr;
TWindow **info;
Boolean saveEncodedText, saveThreadsToSeparateFiles;
info = (TWindow**)GetWRefCon(wind);
saveEncodedText = gPrefs.saveEncodedText;
saveThreadsToSeparateFiles = gPrefs.saveThreadsToSeparateFiles;
FormFileName(wind, fileName);
if (gPrefs.savedArtDefaultFolder && (modifiers & optionKey) == 0) {
err = CheckArticleFileExists(fileName, &fSpec, &scriptTag, &append,
&saveEncodedText, &saveThreadsToSeparateFiles);
if (err != noErr) return err;
} else {
err = PresentStandardArticleSaveFileDialog(fileName, &fSpec, &scriptTag,
&saveEncodedText, &saveThreadsToSeparateFiles);
if (err != noErr) return err;
}
return SaveArticleWindToFile(wind, modifiers, &fSpec, scriptTag, append,
saveEncodedText);
}
/*----------------------------------------------------------------------------
DoSaveAs
Handle the "Save As" command.
Entry: wind = pointer to article window.
modifiers = modifiers field from event record.
Exit: function result = error code.
----------------------------------------------------------------------------*/
static OSErr DoSaveAs (WindowPtr wind, short modifiers)
{
Str31 fileName;
FSSpec fSpec;
ScriptCode scriptTag;
OSErr err = noErr;
TWindow **info;
Boolean saveEncodedText, saveThreadsToSeparateFiles;
info = (TWindow**)GetWRefCon(wind);
saveEncodedText = gPrefs.saveEncodedText;
saveThreadsToSeparateFiles = gPrefs.saveThreadsToSeparateFiles;
FormFileName(wind, fileName);
err = PresentStandardArticleSaveFileDialog(fileName, &fSpec,
&scriptTag, &saveEncodedText, &saveThreadsToSeparateFiles);
if (err != noErr) return err;
return SaveArticleWindToFile(wind, modifiers, &fSpec, scriptTag, false,
saveEncodedText);
}
/*----------------------------------------------------------------------------
DoPrint
Handle the "Print" command.
Entry: wind = pointer to article window.
modifiers = modifiers field from event record.
Exit: function result = error code.
----------------------------------------------------------------------------*/
static OSErr DoPrint (WindowPtr wind, short modifiers)
{
TWindow **info;
TEHandle theTE;
Handle text, fullText;
CStr255 subject, from, str;
OSErr err = noErr;
long start, end;
info = (TWindow**)GetWRefCon(wind);
fullText = (**info).fullText;
theTE = (**info).theTE;
start = (**theTE).selStart;
end = (**theTE).selEnd;
err = StartPrint();
if (err != noErr) return err;
err = DisplayStatusMessageNumber(kStrPrinting);
if (err != noErr) return err;
FindHeaderCString(fullText, "Subject", subject, sizeof(subject));
if (FindHeaderCString(fullText, "From", from, sizeof(from))) {
FormatAuthorName(from);
sprintf(str, "%.40s, %.100s", from, subject);
} else {
strcpy(str, subject);
}
if ((modifiers & shiftKey) == 0 || start >= end) {
text = fullText;
start = 0;
end = MyGetHandleSize(text);
} else {
text = (**theTE).hText;
}
return PrintText(text, start, end, str);
}
/*----------------------------------------------------------------------------
DoAppend
Handle the "Append" command.
Entry: wind = pointer to article window.
modifiers = modifiers field from event record.
Exit: function result = error code.
----------------------------------------------------------------------------*/
static OSErr DoAppend (WindowPtr wind, short modifiers)
{
FSSpec fSpec;
OSErr err = noErr;
TWindow **info;
Boolean saveEncodedText;
info = (TWindow**)GetWRefCon(wind);
saveEncodedText = gPrefs.saveEncodedText;
err = PresentStandardArticleGetFileDialog(&fSpec, &saveEncodedText);
if (err != noErr) return err;
return SaveArticleWindToFile(wind, modifiers, &fSpec, smSystemScript, true,
saveEncodedText);
}
/*----------------------------------------------------------------------------
DoCopy
Handle the "Copy" command.
Entry: wind = pointer to article window.
----------------------------------------------------------------------------*/
static void DoCopy (WindowPtr wind)
{
TWindow **info;
TEHandle theTE;
info = (TWindow**)GetWRefCon(wind);
theTE = (**info).theTE;
MyTECopy(theTE);
}
/*----------------------------------------------------------------------------
DoSelectAll
Handle the "Select All" command.
Entry: wind = pointer to article window.
----------------------------------------------------------------------------*/
static void DoSelectAll (WindowPtr wind)
{
TWindow **info;
TEHandle theTE;
info = (TWindow**)GetWRefCon(wind);
theTE = (**info).theTE;
TESetSelect(0, kMaxShort, theTE);
}
/*----------------------------------------------------------------------------
DoFind
Handle the "Find" command for an article window.
Entry: wind = pointer to article window.
Exit: function result = error code.
----------------------------------------------------------------------------*/
static OSErr DoFind (WindowPtr wind)
{
TWindow **info;
OSErr err = noErr;
TEHandle theTE;
err = DoFindDialog();
if (err != noErr) return err;
info = (TWindow**)GetWRefCon(wind);
theTE = (**info).theTE;
return Find(wind, gPrefs.startFindAtBeginning ? -1 : (**theTE).selStart);
}
/*----------------------------------------------------------------------------
DoFindAgain
Handle the "Find Again" command for an article window.
Entry: wind = pointer to article window.
Exit: function result = error code.
----------------------------------------------------------------------------*/
static OSErr DoFindAgain (WindowPtr wind)
{
TWindow **info;
TEHandle theTE;
info = (TWindow**)GetWRefCon(wind);
theTE = (**info).theTE;
return Find(wind, (**theTE).selEnd);
}
/*----------------------------------------------------------------------------
DoEnterSelection
Handle the "Enter Selection" command for an article window.
Entry: wind = pointer to article window.
Exit: function result = error code.
----------------------------------------------------------------------------*/
static OSErr DoEnterSelection (WindowPtr wind)
{
TWindow **info;
TEHandle theTE;
short selStart, selEnd, len;
Handle hText;
info = (TWindow**)GetWRefCon(wind);
theTE = (**info).theTE;
selStart = (**theTE).selStart;
selEnd = (**theTE).selEnd;
hText = (**theTE).hText;
if (selStart >= selEnd || selEnd > selStart + 255) return noErr;
len = selEnd - selStart;
BlockMoveData(*hText + selStart, gFindPattern, len);
gFindPattern[len] = 0;
return noErr;
}
/*----------------------------------------------------------------------------
DoShowHideDetails
Handle the "Show/Hide Details" command.
Entry: wind = pointer to article window.
Exit: function result = error code.
----------------------------------------------------------------------------*/
static OSErr DoShowHideDetails (WindowPtr wind)
{
TWindow **info;
Boolean showDetails;
short curSection;
TEHandle theTE;
Handle text;
long **breaks;
long length, offset;
Rect textRect;
OSErr err = noErr;
char state;
info = (TWindow**)GetWRefCon(wind);
theTE = (**info).theTE;
showDetails = (**info).showDetails = !(**info).showDetails;
SetEditMenuShowHideDetails(!showDetails);
curSection = (**info).curSection;
breaks = (**info).sectionBreaks;
GetTextRect(wind, &textRect);
if (curSection != 0) ScrollActionSection((**info).hScroll, kScrollToHome);
TEScrollScrollByPartCode(theTE, (**info).vScroll, kScrollToHome);
text = (**info).fullText;
offset = 0;
length = (*breaks)[1];
if (!showDetails) {
offset = FindBody(text);
if (offset > length) offset = length;
length -= offset;
}
state = MyHGetState(text);
MyHLock(text);
err = MyTESetText(*text + offset, length, theTE);
MyHSetState(text, state);
if (err != noErr) return err;
TESetSelect(0, 0, theTE);
TEScrollAdjustScrollMax(theTE, (**info).vScroll);
InvalRect(&textRect);
if (showDetails && gPrefs.reZoomWindows &&
GetControlMaximum((**info).vScroll) > 0 && !(**info).windPosLocked)
{
err = DoZoom(wind, inZoomOut);
if (err != noErr) return err;
} else {
SetWindowNeedsZooming(wind);
}
return noErr;
}
/*----------------------------------------------------------------------------
DoRot13
Handle the "Rot13" command.
Entry: wind = pointer to article window.
----------------------------------------------------------------------------*/
static void DoRot13 (WindowPtr wind)
{
TWindow **info;
TEHandle theTE;
Rect r;
short selStart, selEnd, curSection;
Boolean showDetails;
Handle hText, text;
long **breaks;
long offset;
info = (TWindow**)GetWRefCon(wind);
showDetails = (**info).showDetails;
theTE = (**info).theTE;
curSection = (**info).curSection;
text = (**info).fullText;
breaks = (**info).sectionBreaks;
selStart = (**theTE).selStart;
selEnd = (**theTE).selEnd;
if (selStart >= selEnd) {
selStart = 0;
selEnd = (**theTE).teLength;
}
hText = (**theTE).hText;
offset = curSection == 0 ? 0 : (*breaks)[curSection];
Rot13Text(hText, selStart, selEnd);
r = (**theTE).viewRect;
InvalRect(&r);
if (curSection == 0 && !showDetails) offset += FindBody(text);
BlockMoveData(*hText + selStart, *text + offset + selStart, selEnd - selStart);
}
/*----------------------------------------------------------------------------
DoReply
Handle the "Reply" command.
Entry: wind = pointer to article window.
modifiers = modifiers field from event record.
Exit: function result = error code.
----------------------------------------------------------------------------*/
static OSErr DoReply (WindowPtr wind, short modifiers)
{
TWindow **info;
Handle text, quoteText;
long start, end;
TEHandle theTE;
info = (TWindow**)GetWRefCon(wind);
text = (**info).fullText;
if ((modifiers & shiftKey) == 0) {
quoteText = nil;
} else {
theTE = (**info).theTE;
quoteText = (**theTE).hText;
start = (**theTE).selStart;
end = (**theTE).selEnd;
}
return OpenReplyWindow(text, quoteText, start, end, (modifiers & optionKey) != 0);
}
/*----------------------------------------------------------------------------
DoForward
Handle the "Forward" command.
Entry: wind = pointer to article window.
modifiers = modifiers field from event record.
Exit: function result = error code.
----------------------------------------------------------------------------*/
static OSErr DoForward (WindowPtr wind, short modifiers)
{
TWindow **info;
Handle text, quoteText;
long start, end;
TEHandle theTE;
info = (TWindow**)GetWRefCon(wind);
text = (**info).fullText;
if ((modifiers & shiftKey) == 0) {
quoteText = nil;
} else {
theTE = (**info).theTE;
quoteText = (**theTE).hText;
start = (**theTE).selStart;
end = (**theTE).selEnd;
}
return OpenForwardWindow(text, quoteText, start, end, (modifiers & optionKey) != 0);
}
/*----------------------------------------------------------------------------
DoRedirect
Handle the "Redirect" command.
Entry: wind = pointer to article window.
modifiers = modifiers field from event record.
Exit: function result = error code.
----------------------------------------------------------------------------*/
static OSErr DoRedirect (WindowPtr wind, short modifiers)
{
TWindow **info;
Handle text, quoteText;
long start, end;
TEHandle theTE;
info = (TWindow**)GetWRefCon(wind);
text = (**info).fullText;
if ((modifiers & shiftKey) == 0) {
quoteText = nil;
} else {
theTE = (**info).theTE;
quoteText = (**theTE).hText;
start = (**theTE).selStart;
end = (**theTE).selEnd;
}
return OpenRedirectWindow(text, quoteText, start, end, (modifiers & optionKey) != 0);
}
/*----------------------------------------------------------------------------
DoExtractBinaries
Handle the "Extract Binaries" command.
Entry: wind = pointer to article window.
modifiers = modifiers field from event record.
Exit: function result = error code.
----------------------------------------------------------------------------*/
static OSErr DoExtractBinaries (WindowPtr wind, short modifiers)
{
return ExtractBinariesForArticleWindow(wind, modifiers);
}
/*----------------------------------------------------------------------------
DoOpenAllReferences
Handle the "Open all References" command.
Entry: wind = pointer to article window.
Exit: function result = error code.
----------------------------------------------------------------------------*/
static OSErr DoOpenAllReferences (WindowPtr wind)
{
WindowPtr newWind;
TWindow **info;
Handle fullText, references = nil;
CStr255 msgId;
short totalRefs = 0, totalOpenedRefs = 0;
char *p, *q;
long len;
OSErr err = noErr;
info = (TWindow**)GetWRefCon(wind);
fullText = (**info).fullText;
err = FindHeaderHandle(fullText, "References", &references);
if (err != noErr) goto exit;
if (references == nil) {
NoteMessageNumber(kStrNoRefs);
return noErr;
}
MyHLock(references);
q = *references + MyGetHandleSize(references) - 1;
while (q >= *references) {
while (*q != '>' && q >= *references) q--;
p = q-1;
while (*p != '<' && p >= *references) p--;
len = q - p + 1;
q = p - 1;
if (len <= 2 || len > 255) continue;
BlockMoveData(p, msgId, len);
*(msgId + len) = 0;
totalRefs++;
if (CheckAlreadyOpen(msgId)) {
totalOpenedRefs++;
} else {
err = GetArticleAndMakeNewWindow(nil, 0, nil, 0, msgId, nil, false, nil, true,
&newWind);
if (err != noErr) goto exit;
if (newWind != nil) {
totalOpenedRefs++;
MyShowWindow(newWind);
}
}
}
MyDisposeHandle(references);
if (totalOpenedRefs < totalRefs) {
if (totalOpenedRefs == 0) {
NoteMessageNumber(kStrNoneOpened);
} else {
NoteMessageNumber(kStrSomeNotOpened);
}
}
return noErr;
exit:
MyDisposeHandle(references);
return err;
}
/*----------------------------------------------------------------------------
DoCancelArticle
Handles the "Cancel Article" command.
Entry: wind = pointer to article window.
Exit: function result = error code.
----------------------------------------------------------------------------*/
static OSErr DoCancelArticle (WindowPtr wind)
{
TWindow **info;
Handle text;
Handle groups = nil;
CStr255 idStr, statusMsg;
OSErr err = noErr;
info = (TWindow**)GetWRefCon(wind);
text = (**info).fullText;
if (!UserIsPoster(text)) goto exit1;
GetCString(kStrCancelingStatusMsg, statusMsg);
err = DisplayStatusMessage(statusMsg);
if (err != noErr) return err;
if (!FindHeaderCString(text, "Message-ID", idStr, sizeof(idStr))) goto exit2;
err = FindHeaderHandle(text, "Newsgroups", &groups);
if (err != noErr) goto exit3;
if (groups == nil) goto exit4;
err = CancelArticle(idStr, groups, statusMsg);
if (err != noErr) goto exit3;
MyDisposeHandle(groups);
return noErr;
exit1:
MyDisposeHandle(groups);
ErrorMessageNumber(kStrNotCanceled);
return userCanceledErr;
exit2:
MyDisposeHandle(groups);
ErrorMessageNumber(kStrNoMsgID);
return userCanceledErr;
exit3:
MyDisposeHandle(groups);
return err;
exit4:
MyDisposeHandle(groups);
ErrorMessageNumber(kStrNoNewsgroupsHeader);
return userCanceledErr;
}
/*----------------------------------------------------------------------------
Activate
Handle an activate event for an article window.
Entry: wind = pointer to article window.
act = true to activate, false to deactivate
----------------------------------------------------------------------------*/
static void Activate (WindowPtr wind, Boolean act)
{
TWindow **info;
TEHandle theTE;
ControlHandle vScroll, hScroll;
Rect r;
info = (TWindow**)GetWRefCon(wind);
theTE = (**info).theTE;
vScroll = (**info).vScroll;
hScroll = (**info).hScroll;
if (act) {
ShowControl(vScroll);
if (hScroll != nil) ShowControl(hScroll);
TEActivate(theTE);
} else {
HideControl(vScroll);
if (hScroll != nil) HideControl(hScroll);
TEDeactivate(theTE);
}
r = wind->portRect;
r.top = r.bottom - 15;
r.left = r.right - 15;
InvalRect(&r);
}
/*----------------------------------------------------------------------------
Update
Handle an update event for an article window.
Entry: wind = pointer to article window.
----------------------------------------------------------------------------*/
static void Update (WindowPtr wind)
{
TWindow **info, **parentInfo;
short panelHeight, windWidth, lineHeight;
Rect r;
TEHandle theTE;
WindowPtr parent;
TSubject subject, **subjectArray;
Handle fullText;
CStr255 from, organization, date, newsgroups;
CStr255 followupto, replyto;
CStr255 articleXofYFormat;
char msg[600];
short len, threadInfoWidth, v;
char c;
Rect iconRect;
short maxPanelStringWidth;
FontInfo fontInfo;
info = (TWindow**)GetWRefCon(wind);
panelHeight = (**info).panelHeight;
theTE = (**info).theTE;
fullText = (**info).fullText;
fontInfo = (**info).fontInfo;
lineHeight = (**info).lineHeight;
if (lineHeight < 11) lineHeight = 11;
r = wind->portRect;
r.top += panelHeight;
ClipRect(&r);
DrawGrowIcon(wind);
NoClip();
UpdateControls(wind, wind->visRgn);
windWidth = wind->portRect.right - wind->portRect.left;
MoveTo(0, panelHeight-3);
LineTo(windWidth, panelHeight-3);
MoveTo(0, panelHeight-1);
LineTo(windWidth, panelHeight-1);
if (CanExtractBinaries(wind, &iconRect)) {
PlotIconID(&iconRect, 0, ttNone, kFileIconID);
maxPanelStringWidth = iconRect.left - 20;
} else if ((**info).parentWindow != nil) {
maxPanelStringWidth = windWidth - 40;
} else {
maxPanelStringWidth = windWidth - 20;
}
if ((**info).parentWindow != nil) DrawArrowPair((**info).nextPrevArrowPair);
FindHeaderCString(fullText, "From", from, sizeof(from));
GetCString(kStrFrom, msg);
strcat(msg, from);
v = fontInfo.ascent + 3;
DrawPanelString(msg, v, maxPanelStringWidth);
FindHeaderCString(fullText, "Organization", organization, sizeof(organization));
GetCString(kStrOrg, msg);
strcat(msg, organization);
v += lineHeight;
DrawPanelString(msg, v, maxPanelStringWidth);
FindHeaderCString(fullText, "NewsGroups", newsgroups, sizeof(date));
GetCString(kStrNewsgroups, msg);
strcat(msg, newsgroups);
v += lineHeight;
DrawPanelString(msg, v, maxPanelStringWidth);
if ((**info).showFollowupTo) {
FindHeaderCString(fullText, "Followup-To", followupto, sizeof(followupto));
GetCString(kStrFollowupto, msg);
strcat(msg, followupto);
v += lineHeight;
DrawPanelString(msg, v, maxPanelStringWidth);
}
if ((**info).showReplyTo) {
FindHeaderCString(fullText, "Reply-To", replyto, sizeof(replyto));
GetCString(kStrReplyto, msg);
strcat(msg, replyto);
v += lineHeight;
DrawPanelString(msg, v, maxPanelStringWidth);
}
v += lineHeight;
threadInfoWidth = 0;
parent = (**info).parentWindow;
if (parent != nil) {
parentInfo = (TWindow**)GetWRefCon(parent);
subjectArray = (**parentInfo).subjectArray;
subject = (*subjectArray)[(**info).parentSubject];
if (subject.threadLength > 1) {
c = subject.threadOrdinal == subject.threadLength ? '•' : '…';
GetCString(kStrArticleXofY, articleXofYFormat);
sprintf(msg, articleXofYFormat, subject.threadOrdinal, subject.threadLength, c);
len = strlen(msg);
threadInfoWidth = TextWidth(msg, 0, len);
MoveTo(windWidth - 10 - threadInfoWidth, v);
DrawText(msg, 0, len);
}
}
(**info).threadInfoWidth = threadInfoWidth;
FindHeaderCString(fullText, "Date", date, sizeof(date));
Cleanup822Date(date);
GetCString(kStrDate, msg);
strcat(msg, date);
DrawPanelString(msg, v, windWidth-20-threadInfoWidth);
if ((**info).numSections > 1) DrawSectionMessage(wind);
TEUpdate(&wind->portRect, theTE);
DrawPadlockIcon(wind);
}
/*----------------------------------------------------------------------------
Mouse
Handle a mouse down event in the content area of an article window.
Entry: wind = pointer to article window.
where = location of mouse down in local coords.
modifiers = modifiers field from event record.
Exit: function result = error code.
----------------------------------------------------------------------------*/
static OSErr Mouse (WindowPtr wind, Point where, short modifiers)
{
TWindow **info;
TEHandle theTE;
Rect viewRect, iconRect;
short part, oldVal, dv,dh;
ControlHandle control, vScroll, hScroll;
OSErr err = noErr;
Boolean dragged = false, trashed;
short oldSelStart, oldSelEnd, trackArrowPairResult;
Boolean Draggable (WindowPtr wind, Point where, short modifiers);
if (HandlePadlockClick(wind, where, &gPrefs.articleWindPosLocked,
&gPrefs.articleWindLocn)) return noErr;
info = (TWindow**) GetWRefCon(wind);
theTE = (**info).theTE;
vScroll = (**info).vScroll;
hScroll = (**info).hScroll;
if ((**info).sectionArrowPair != nil) {
trackArrowPairResult = TrackArrowPair((**info).sectionArrowPair);
if (trackArrowPairResult == -1) {
ScrollActionSection(hScroll, inUpButton);
return noErr;
} else if (trackArrowPairResult == +1) {
ScrollActionSection(hScroll, inDownButton);
return noErr;
}
}
if ((**info).nextPrevArrowPair != nil) {
trackArrowPairResult = TrackArrowPair((**info).nextPrevArrowPair);
if (trackArrowPairResult != 0) {
return GoBackwardsOrForwardsOneArticle(wind, trackArrowPairResult);
}
}
viewRect = (**theTE).viewRect;
InsetRect(&viewRect, -kTextMargin, 0);
part = FindControl(where, wind, &control);
if (part != 0) {
if (control == vScroll) {
if (part == inThumb) {
oldVal = GetControlValue(vScroll);
TrackControl(vScroll, where, nil);
dv = GetControlValue(vScroll) - oldVal;
if (dv != 0) TEScrollScrollText(theTE, vScroll, -dv);
} else {
SetControlReference(vScroll, 0);
TrackControl(vScroll, where, gScrollActionUPP);
SetControlReference(vScroll, 1);
TEScrollAdjustScrollMax(theTE, vScroll);
}
} else if (control == hScroll) {
if (part == inThumb) {
oldVal = GetControlValue(hScroll);
TrackControl(hScroll, where, nil);
dh = GetControlValue(hScroll) - oldVal;
if (dh != 0) ScrollSection(wind, -dh);
} else {
TrackControl(hScroll, where, gScrollActionSectionUPP);
}
}
} else if (PtInRect(where, &viewRect)) {
if (gHaveDragMgr) {
err = DragText(&gCurEvent, where, theTE, &dragged, &trashed);
if (err != noErr) return err;
}
if (wind == FrontWindow() && !dragged) {
oldSelStart = (**theTE).selStart;
oldSelEnd = (**theTE).selEnd;
MyTEClick(where, (modifiers & shiftKey) != 0, theTE);
err = CommandClick(wind, theTE, oldSelStart, oldSelEnd, modifiers);
if (err != noErr) return err;
}
} else if (CanExtractBinaries(wind, &iconRect) && PtInRect(where, &iconRect) &&
Draggable(wind, where, 0))
{
if (gHaveDragMgr) {
if ((**info).parentWindow == nil) return TooStupidAlert();
return DragAttachedFileIcon(wind, &iconRect, modifiers);
} else if (TrackIconClick(where, &iconRect, nil, kFileIconID)) {
return ExtractBinariesForArticleWindow(wind, modifiers);
}
}
return noErr;
}
/*----------------------------------------------------------------------------
Draggable
Determine whether a mouse down event is on a draggable object in an
article window.
Entry: wind = pointer to article window.
where = location of mouse down event, in local coordinates.
modifiers = modifiers field from event record.
Exit: function result = true if object is draggable.
----------------------------------------------------------------------------*/
static Boolean Draggable (WindowPtr wind, Point where, short modifiers)
{
Rect iconRect;
RgnHandle iconRgn;
OSErr err = noErr;
Boolean result;
TWindow **info;
TEHandle theTE;
if (CanExtractBinaries(wind, &iconRect)) {
iconRgn = NewRgn();
err = IconIDToRgn(iconRgn, &iconRect, ttNone, kFileIconID);
result = err == noErr && PtInRgn(where, iconRgn);
DisposeRgn(iconRgn);
if (result) return true;
}
info = (TWindow**)GetWRefCon(wind);
theTE = (**info).theTE;
return PtInTEHiliteRgn(where, theTE);
}
/*----------------------------------------------------------------------------
Key
Handle a key down event for an article window.
Entry: wind = pointer to article window.
theChar = ASCII code of key.
theKey = keyboard code of key.
modifiers = modifiers field from event record.
Exit: function result = error code.
----------------------------------------------------------------------------*/
static OSErr Key (WindowPtr wind, unsigned char theChar, unsigned char theKey,
short modifiers)
{
TWindow **info;
ControlHandle vScroll, hScroll;
TEHandle theTE;
OSErr err = noErr;
short scrollIntoView;
TKeypadKey keypadKey;
Boolean isArrow, first, last;
info = (TWindow**)GetWRefCon(wind);
vScroll = (**info).vScroll;
hScroll = (**info).hScroll;
theTE = (**info).theTE;
isArrow = IsArrowKey(theChar);
if ((modifiers & cmdKey) != 0 && !isArrow) {
if ((**info).parentWindow != nil) {
CheckFirstLastArticle(wind, &first, &last);
if (theChar == '1') {
if (first) {
SysBeep(0);
return noErr;
}
return GoBackwardsOrForwardsOneArticle(wind, -1);
} else if (theChar == '2') {
if (last) {
SysBeep(0);
return noErr;
}
return GoBackwardsOrForwardsOneArticle(wind, +1);
}
}
SysBeep(0);
return noErr;
}
if (gPrefs.keypadShortcuts && IsKeypadKey(theChar, theKey, &keypadKey)) {
switch (keypadKey) {
case kKeypadEqualKey:
DoSelectAll(wind);
return noErr;
case kKeypadStarKey:
return DoClose(wind);
case kKeypadMinusKey:
return DoMarkCommand(wind, false);
case kKeypadPlusKey:
return DoMarkCommand(wind, true);
case kKeypadEnterKey:
return DoNextGroup(wind);
case kKeypadPeriodKey:
return DoNextThread(wind);
case kKeypad0Key:
return DoNextArticle(wind);
case kKeypad1Key:
TEScrollScrollByPartCode(theTE, vScroll, kScrollToEnd);
return noErr;
case kKeypad2Key:
TEScrollScrollByPartCode(theTE, vScroll, inDownButton);
return noErr;
case kKeypad3Key:
TEScrollScrollByPartCode(theTE, vScroll, inPageDown);
return noErr;
case kKeypad4Key:
if (hScroll != nil) {
ScrollActionSection(hScroll, inUpButton);
} else {
SysBeep(0);
}
return noErr;
case kKeypad5Key:
if (GetControlValue(vScroll) < GetControlMaximum(vScroll)) {
TEScrollScrollByPartCode(theTE, vScroll, inPageDown);
} else if (hScroll != nil &&
GetControlValue(hScroll) < GetControlMaximum(hScroll))
{
ScrollActionSection(hScroll, inDownButton);
} else {
return DoNextArticle(wind);
}
return noErr;
case kKeypad6Key:
if (hScroll != nil) {
ScrollActionSection(hScroll, inDownButton);
} else {
SysBeep(0);
}
return noErr;
case kKeypad7Key:
TEScrollScrollByPartCode(theTE, vScroll, kScrollToHome);
return noErr;
case kKeypad8Key:
TEScrollScrollByPartCode(theTE, vScroll, inUpButton);
return noErr;
case kKeypad9Key:
TEScrollScrollByPartCode(theTE, vScroll, inPageUp);
return noErr;
default:
SysBeep(0);
return noErr;
}
}
if (theChar == pageUpKey) {
TEScrollScrollByPartCode(theTE, vScroll, inPageUp);
return noErr;
}
if (theChar == pageDownKey) {
TEScrollScrollByPartCode(theTE, vScroll, inPageDown);
return noErr;
}
if (theChar == homeKey) {
TEScrollScrollByPartCode(theTE, vScroll, kScrollToHome);
return noErr;
}
if (theChar == endKey) {
TEScrollScrollByPartCode(theTE, vScroll, kScrollToEnd);
return noErr;
}
if (isArrow) {
TEArrowKey(theChar, modifiers, theTE, 0, &gPrevEvent, &scrollIntoView);
TEScrollScrollRangeIntoView(theTE, scrollIntoView, scrollIntoView, vScroll);
return noErr;
}
if (gPrefs.keyboardShortcuts) {
if (theChar == ' ') {
if (GetControlValue(vScroll) < GetControlMaximum(vScroll)) {
TEScrollScrollByPartCode(theTE, vScroll, inPageDown);
} else if (hScroll != nil && GetControlValue(hScroll) < GetControlMaximum(hScroll)) {
ScrollActionSection(hScroll, inDownButton);
} else {
return DoNextArticle(wind);
}
return noErr;
}
theChar = tolower(theChar);
if (theChar == 'n' || theChar == 'i') {
return DoNextArticle(wind);
}
if (theChar == 't') {
return DoNextThread(wind);
}
if (theChar == 'g' || theChar == 'j') {
return DoNextGroup(wind);
}
if (theChar == 'w') {
return DoClose(wind);
}
if (theChar == 'u') {
return DoMarkCommand(wind, false);
}
if (theChar == 'm') {
return DoMarkCommand(wind, true);
}
if (theChar == 'a') {
DoSelectAll(wind);
return noErr;
}
}
SysBeep(0);
return noErr;
}
/*----------------------------------------------------------------------------
Grow
Handle a mouse down event in the grow box of an article window.
Entry: wind = pointer to article window.
where = location of mouse down event, in global coordinates.
Exit: function result = error code.
----------------------------------------------------------------------------*/
static OSErr Grow (WindowPtr wind, Point where)
{
Rect sizeRect;
long size;
short width, height;
SetRect(&sizeRect, kMinWindowWidth, MinHeight(wind), kMaxShort, kMaxShort);
size = GrowWindow(wind, where, &sizeRect);
if (size != 0) {
width = LoWord(size);
height = HiWord(size);
FixHeight(wind, &height);
SizeWindow(wind, width, height, false);
ResizeContents(wind);
}
return noErr;
}
/*----------------------------------------------------------------------------
Zoom
Zoom an article window.
Entry: wind = pointer to article window.
zoomDir = zoom direction = inZoomIn, inZoomOut, or
zoomOutOnlyIfBigger.
Exit: function result = error code.
----------------------------------------------------------------------------*/
static OSErr Zoom (WindowPtr wind, short zoomDir)
{
TWindow **info;
short width, height, lineHeight, minHeight, lineWidth, maxWidth, maxBodyLineWidth;
Rect zoomRect, r;
WStateData **wState;
TEHandle theTE, tempTE = nil;
Handle fullText;
long numBodyLines, longHeight, offset, length, maxHeight;
char *p, *pEnd, *lineStart;
long **breaks;
short numSections, i;
OSErr err = noErr;
char state;
FontInfo fontInfo;
wState = (WStateData**)((WindowPeek)wind)->dataHandle;
if (zoomDir == inZoomOut || zoomDir == zoomOutOnlyIfBigger) {
info = (TWindow**)GetWRefCon(wind);
fontInfo = (**info).fontInfo;
lineHeight = (**info).lineHeight;
theTE = (**info).theTE;
fullText = (**info).fullText;
maxBodyLineWidth = (**info).maxBodyLineWidth;
numBodyLines = (**info).numBodyLines;
if (maxBodyLineWidth == 0) {
maxWidth = 80 * fontInfo.widMax;
numBodyLines = 0;
state = MyHGetState(fullText);
MyHLock(fullText);
for (p = *fullText + FindBody(fullText),
pEnd = *fullText + MyGetHandleSize(fullText);
p < pEnd;
p++)
{
lineStart = p;
while (p < pEnd && *p != CR) p++;
numBodyLines++;
if (maxBodyLineWidth < maxWidth) {
lineWidth = TextWidth(lineStart, 0, p - lineStart);
if (lineWidth > maxBodyLineWidth) maxBodyLineWidth = lineWidth;
}
}
MyHSetState(fullText, state);
if (maxBodyLineWidth > maxWidth) maxBodyLineWidth = maxWidth;
(**info).maxBodyLineWidth = maxBodyLineWidth;
(**info).numBodyLines = numBodyLines;
}
width = maxBodyLineWidth + 2*kTextMargin + 18;
if (width < kMinWindowWidth) width = kMinWindowWidth;
if (numBodyLines > 100) {
height = kMaxShort;
} else {
CalculateZoomRect(wind, width, kMaxShort, &zoomRect, gPrefs.dontCoverFinderIcons);
SetRect(&r, 0, 0, zoomRect.right - zoomRect.left - 15 - 2*kTextMargin, kMaxShort);
tempTE = TENew(&r, &r);
breaks = (**info).sectionBreaks;
numSections = (**info).numSections;
maxHeight = 0;
for (i = 0; i < numSections; i++) {
offset = (*breaks)[i];
length = (*breaks)[i+1] - offset;
if (i == 0 && !(**info).showDetails) {
offset = FindBody(fullText);
if (offset > length) offset = length;
length -= offset;
}
state = MyHGetState(fullText);
MyHLock(fullText);
err = MyTESetText(*fullText + offset, length, tempTE);
MyHSetState(fullText, state);
if (err != noErr) goto exit;
longHeight = (long)TEScrollNumTELines(tempTE) * (long)lineHeight;
longHeight += (**info).panelHeight + 15 + 2*kTextMargin;
if (longHeight > maxHeight) maxHeight = longHeight;
}
TEDispose(tempTE);
if (maxHeight > kMaxShort) {
height = kMaxShort;
} else {
height = maxHeight;
minHeight = MinHeight(wind);
if (height < minHeight) height = minHeight;
}
}
if (zoomDir == zoomOutOnlyIfBigger) {
if (width <= wind->portRect.right && height <= wind->portRect.bottom) {
SetWindowNeedsZooming(wind);
return noErr;
}
if (width < wind->portRect.right) width = wind->portRect.right;
if (height < wind->portRect.bottom) height = wind->portRect.bottom;
}
CalculateZoomRect(wind, width, height, &zoomRect, gPrefs.dontCoverFinderIcons);
height = zoomRect.bottom - zoomRect.top;
FixHeight(wind, &height);
zoomRect.bottom = zoomRect.top + height;
(**wState).stdState = zoomRect;
if (WindRectEqualRect(wind, &zoomRect)) return noErr;
}
EraseRect(&wind->portRect);
ZoomWindow(wind, zoomDir, false);
ResizeContents(wind);
return noErr;
exit:
if (tempTE != nil) TEDispose(tempTE);
return err;
}
/*----------------------------------------------------------------------------
Command
Handle a command for an article window.
Entry: wind = pointer to article window.
menu = the menu.
item = the item.
modifiers = modifiers field from event record.
Exit: function result = error code.
----------------------------------------------------------------------------*/
static OSErr Command (WindowPtr wind, short menu, short item, short modifiers)
{
OSErr err = noErr;
switch (menu) {
case kFileMenu:
switch (item) {
case kSaveItem:
err = DoSave(wind, modifiers);
break;
case kSaveAsItem:
err = DoSaveAs(wind, modifiers);
break;
case kAppendItem:
err = DoAppend(wind, modifiers);
break;
case kPrintItem:
err = DoPrint(wind, modifiers);
break;
}
break;
case kEditMenu:
switch (item) {
case kCopyItem:
DoCopy(wind);
break;
case kSelectAllItem:
DoSelectAll(wind);
break;
case kFindItem:
err = DoFind(wind);
break;
case kFindAgainItem:
err = DoFindAgain(wind);
break;
case kEnterSelectionItem:
err = DoEnterSelection(wind);
break;
case kShowHideDetailsItem:
err = DoShowHideDetails(wind);
break;
case kRot13Item:
DoRot13(wind);
break;
}
break;
case kNewsMenu:
switch (item) {
case kNextArticleItem:
err = DoNextArticle(wind);
break;
case kNextThreadItem:
err = DoNextThread(wind);
break;
case kNextGroupItem:
err = DoNextGroup(wind);
break;
case kMarkReadItem:
err = DoMarkCommand(wind, true);
break;
case kMarkUnreadItem:
err = DoMarkCommand(wind, false);
break;
case kReplyItem:
err = DoReply(wind, modifiers);
break;
case kForwardItem:
err = DoForward(wind, modifiers);
break;
case kRedirectItem:
err = DoRedirect(wind, modifiers);
break;
}
break;
case kSpecialMenu:
switch (item) {
case kExtractBinariesItem:
err = DoExtractBinaries(wind, modifiers);
break;
case kSearchItem:
err = DoSearch(wind);
break;
case kOpenAllReferencesItem:
err = DoOpenAllReferences(wind);
break;
case kCancelArticleItem:
err = DoCancelArticle(wind);
break;
}
break;
}
return err;
}
/*----------------------------------------------------------------------------
Close
Close an article window.
Entry: wind = pointer to article window.
Exit: function result = error code.
----------------------------------------------------------------------------*/
static OSErr Close (WindowPtr wind)
{
TWindow **info;
info = (TWindow**)GetWRefCon(wind);
RemoveChild((**info).parentWindow, wind);
SaveArticleInCache(wind);
if ((**info).theTE != nil) TEDispose((**info).theTE);
MyDisposeHandle((**info).sectionBreaks);
DisposeArrowPair((**info).sectionArrowPair);
DisposeArrowPair((**info).nextPrevArrowPair);
MyDisposeHandle(info);
MyDisposeWindow(wind);
return noErr;
}
/*----------------------------------------------------------------------------
Idle
Handle idle time tasks for an article window.
Entry: wind = pointer to article window.
Exit: cursorRgn = cursor region for WaitNextEvent mouse moved events.
----------------------------------------------------------------------------*/
static void Idle (WindowPtr wind, RgnHandle cursorRgn)
{
TWindow **info;
TEHandle theTE;
Rect r;
Point where;
unsigned long newsEnabled, editEnabled, specialEnabled;
CStr255 refs;
short selStart, selEnd;
Rect iconRect;
info = (TWindow**)GetWRefCon(wind);
theTE = (**info).theTE;
TEIdle(theTE);
GetTextRect(wind, &r);
InsetRect(&r, -kTextMargin, 0);
LocalToGlobalRect(&r);
RectRgn(cursorRgn, &r);
if (gHaveDragMgr) SubtractTEHiliteRgn(cursorRgn, theTE);
GetMouse(&where);
LocalToGlobal(&where);
if (PtInRgn(where, cursorRgn)) {
SetCursor(&gIBeamCurs);
} else {
SetCursor(&qd.arrow);
ComplementRgn(cursorRgn);
}
newsEnabled = (**info).parentWindow == nil ? kArticleNewsEnabledNoParent :
kArticleNewsEnabled;
editEnabled = kArticleEditEnabled;
specialEnabled = kArticleSpecialEnabled;
selStart = (**theTE).selStart;
selEnd = (**theTE).selEnd;
if (selStart >= selEnd) {
editEnabled &= ~(kCopyMask | kEnterSelectionMask);
} else if (selEnd > selStart + 255) {
editEnabled &= ~kEnterSelectionMask;
}
if ((**theTE).teLength == 0) editEnabled &= ~(kSelectAllMask | kRot13Mask);
if (!FindHeaderCString((**info).fullText, "References", refs, sizeof(refs)))
specialEnabled &= ~kOpenAllReferencesMask;
/* Following line of code disabled because it can cause repeated disk hits
with Internet Config.
if (!UserIsPoster((**info).fullText)) specialEnabled &= ~kCancelArticleMask;
*/
if (!CanExtractBinaries(wind, &iconRect)) specialEnabled &= ~kExtractBinariesMask;
if (*gFindPattern == 0) editEnabled &= ~kFindAgainMask;
SetMenusTo(kAppleAllEnabled, kArticleFileEnabled, editEnabled,
newsEnabled, specialEnabled, kArticleWindEnabled);
SetEditMenuShowHideDetails(!(**info).showDetails);
}
/*----------------------------------------------------------------------------
Help
Handle help balloons for an article window.
Entry: wind = pointer to article window.
where = current mouse location in local coordinates.
----------------------------------------------------------------------------*/
static void Help (WindowPtr wind, Point where)
{
TWindow **info;
Rect r;
Point tip = {0,0};
short panelHeight, result, lineHeight, threadInfoWidth;
Boolean enabled, canExtractBinaries;
ControlHandle hScroll;
FontInfo fontInfo;
if (DoSizeBoxAndVerticalScrollBarBalloons(wind, where)) return;
info = (TWindow**)GetWRefCon(wind);
panelHeight = (**info).panelHeight;
threadInfoWidth = (**info).threadInfoWidth;
fontInfo = (**info).fontInfo;
lineHeight = (**info).lineHeight;
if (lineHeight < 11) lineHeight = 11;
SetRect(&r, 0, wind->portRect.bottom - 14, 15, wind->portRect.bottom);
if (PtInRect(where, &r)) {
ShowHelpBalloon(tip, &r, (**info).windPosLocked ? 26 : 25);
return;
}
SetRect(&r, 0, panelHeight,
wind->portRect.right - 15, wind->portRect.bottom - 15);
if (PtInRect(where, &r)) {
ShowHelpBalloon(where, &r, 27);
return;
}
result = TestArrowPairHotRect((**info).nextPrevArrowPair, where, &r, &enabled);
if (result == -1) {
ShowHelpBalloon(tip, &r, enabled ? 28 : 29);
return;
} else if (result == +1) {
ShowHelpBalloon(tip, &r, enabled ? 30 : 31);
return;
}
canExtractBinaries = CanExtractBinaries(wind, &r);
if (canExtractBinaries) {
r.right -= 11;
r.top += 6;
if (PtInRect(where, &r)) {
ShowHelpBalloon(tip, &r, 32);
return;
}
}
r = wind->portRect;
r.left = 5;
r.top = 3;
r.bottom = r.top + lineHeight;
r.right -= 15;
if ((**info).nextPrevArrowPair) r.right -= 12;
if (canExtractBinaries) r.right -= 27;
SetPt(&tip, 10, r.top + fontInfo.ascent);
if (PtInRect(where, &r)) {
ShowHelpBalloon(tip, &r, 33);
return;
}
OffsetRect(&r, 0, lineHeight);
tip.v += lineHeight;
if (PtInRect(where, &r)) {
ShowHelpBalloon(tip, &r, 34);
return;
}
OffsetRect(&r, 0, lineHeight);
tip.v += lineHeight;
if (PtInRect(where, &r)) {
ShowHelpBalloon(tip, &r, 35);
return;
}
if ((**info).showFollowupTo) {
OffsetRect(&r, 0, lineHeight);
tip.v += lineHeight;
if (PtInRect(where, &r)) {
ShowHelpBalloon(tip, &r, 36);
return;
}
}
if ((**info).showReplyTo) {
OffsetRect(&r, 0, lineHeight);
tip.v += lineHeight;
if (PtInRect(where, &r)) {
ShowHelpBalloon(tip, &r, 37);
return;
}
}
OffsetRect(&r, 0, lineHeight);
r.right = wind->portRect.right - threadInfoWidth - 15;
tip.v += lineHeight;
if (PtInRect(where, &r)) {
ShowHelpBalloon(tip, &r, 38);
return;
}
if (threadInfoWidth != 0) {
r.left = wind->portRect.right - threadInfoWidth - 15;
r.right = wind->portRect.right;
if (PtInRect(where, &r)) {
tip.h = r.left + 5;
ShowHelpBalloon(tip, &r, 39);
}
}
SetPt(&tip, 0, 0);
if ((**info).numSections > 1) {
SetRect(&r, 16, wind->portRect.bottom - 15,
kSectionMargin - 30, wind->portRect.bottom);
if (PtInRect(where, &r)) {
ShowHelpBalloon(tip, &r, 40);
}
result = TestArrowPairHotRect((**info).sectionArrowPair, where, &r, &enabled);
if (result == -1) {
ShowHelpBalloon(tip, &r, enabled ? 41 : 42);
return;
} else if (result == +1) {
ShowHelpBalloon(tip, &r, enabled ? 43 : 44);
return;
}
hScroll = (**info).hScroll;
if (hScroll != nil) {
r = (**hScroll).contrlRect;
if (PtInRect(where, &r)) {
ShowHelpBalloon(tip, &r, 45);
return;
}
}
}
}
/*----------------------------------------------------------------------------
InitArticleDispatchTable
Initialize the dispatch table for article windows.
----------------------------------------------------------------------------*/
void InitArticleDispatchTable (void)
{
TDispatch *d;
d = &gDispatch[kArticle];
d->activate = Activate;
d->update = Update;
d->mouse = Mouse;
d->draggable = Draggable;
d->key = Key;
d->grow = Grow;
d->zoom = Zoom;
d->command = Command;
d->close = Close;
d->idle = Idle;
d->help = Help;
gAutoScrollUPP = NewTEClickLoopProc(AutoScroll);
gDragAttachedFileIconSendProcUPP = NewDragSendDataProc(DragAttachedFileIconSendProc);
gScrollActionUPP = NewControlActionProc(ScrollAction);
gScrollActionSectionUPP = NewControlActionProc(ScrollActionSection);
}